From c561f2def9ccea7c6a29aab071af9159ac04fc2f Mon Sep 17 00:00:00 2001 From: WangXuan95 <629708558@qq.com> Date: Thu, 29 Sep 2022 14:19:00 +0800 Subject: [PATCH] update --- README.md | 295 +++++++++ RTL/sd_file_reader.sv | 531 ++++++++++++++++ RTL/sd_reader.sv | 230 +++++++ RTL/sdcmd_ctrl.sv | 133 ++++ SIM/sd_fake.sv | 580 ++++++++++++++++++ SIM/tb_sd_file_reader.sv | 455 ++++++++++++++ SIM/tb_sd_file_reader_run_iverilog.bat | 5 + example-vivado-readfile/Nexys-4-DDR-pins.xdc | 41 ++ example-vivado-readfile/Nexys4-ReadFile.xpr | 234 +++++++ example-vivado-readfile/RTL/top.sv | 84 +++ example-vivado-readfile/RTL/uart_tx.sv | 188 ++++++ .../Nexys-4-DDR-pins.xdc | 41 ++ .../Nexys4-ReadSector.xpr | 228 +++++++ example-vivado-readsector/RTL/top.sv | 96 +++ example-vivado-readsector/RTL/uart_tx.sv | 188 ++++++ figures/pin.png | Bin 0 -> 23691 bytes 16 files changed, 3329 insertions(+) create mode 100644 README.md create mode 100644 RTL/sd_file_reader.sv create mode 100644 RTL/sd_reader.sv create mode 100644 RTL/sdcmd_ctrl.sv create mode 100644 SIM/sd_fake.sv create mode 100644 SIM/tb_sd_file_reader.sv create mode 100644 SIM/tb_sd_file_reader_run_iverilog.bat create mode 100644 example-vivado-readfile/Nexys-4-DDR-pins.xdc create mode 100644 example-vivado-readfile/Nexys4-ReadFile.xpr create mode 100644 example-vivado-readfile/RTL/top.sv create mode 100644 example-vivado-readfile/RTL/uart_tx.sv create mode 100644 example-vivado-readsector/Nexys-4-DDR-pins.xdc create mode 100644 example-vivado-readsector/Nexys4-ReadSector.xpr create mode 100644 example-vivado-readsector/RTL/top.sv create mode 100644 example-vivado-readsector/RTL/uart_tx.sv create mode 100644 figures/pin.png diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb802a5 --- /dev/null +++ b/README.md @@ -0,0 +1,295 @@ +![语言](https://img.shields.io/badge/语言-systemverilog_(IEEE1800_2005)-CAD09D.svg) ![仿真](https://img.shields.io/badge/仿真-iverilog-green.svg) ![部署](https://img.shields.io/badge/部署-quartus-blue.svg) ![部署](https://img.shields.io/badge/部署-vivado-FF1010.svg) + +中文 | [English](#en) + +FPGA SDcard File Reader +=========================== + +基于 FPGA 的 SD卡文件读取器 + +* **功能** :FPGA作为 **SD-host** , 指定文件名读取文件内容;或指定扇区号读取扇区内容。 +* **性能** :使用 SD总线,而不是 SPI总线。读取速度更快。 +* **兼容性强** :自动适配 **SD卡版本** ,自动适配 **FAT16/FAT32文件系统**。 +* 纯 RTL 实现,便于移植和仿真。 + +| | SDv1.1 card | SDv2 card | SDHCv2 card | +| :----- | :------------: | :------------: | :------------: | +| **读取扇区** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| **读取文件 (FAT16)** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| **读取文件 (FAT32)** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | + + + +# 背景知识 + +## SD总线 + +SD卡使用 SD 总线与 SD-host (比如读卡器)连接,SD总线的信号包括: + +| 信号名 | 输入输出方向 | +| ------------------------------ | ------------------------------------------------ | +| sdclk | host→ card | +| sdcmd | 当发起命令时 host→ card ,当响应命令时 card→host | +| sddat0、sddat1、sddat2、sddat3 | 当写数据时 host→card ,当读数据时 card→host | + +这些信号在 SD 卡和 microSD 卡上的引脚定义如下图(SD卡和microSD卡除了外形尺寸外,功能上没有差别) + +| ![pin](./figures/pin.png) | +| :------------------------------------------: | +| 图:SD 卡(左)与 microSD 卡(右)的引脚定义 | + + + +## 文件系统 + +SD卡本身只是一大块线性的数据存储空间,分为多个扇区 (sector),每个扇区 512 字节,扇区0的地址范围为 0x00000000\~0x000001FF,扇区1的地址范围为 0x00000200\~0x000003FF,以此类推……。底层的读取和写入操作都以扇区为单位。为了在这片线性的存储空间中组织磁盘分区和文件,人们规定了复杂的数据结构——文件系统,SD卡最常用的文件系统是 FAT16 和 FAT32 。 + +为了从 SD 卡中读取文件数据,本库分为两个功能模块: + +- 按照 SD 总线标准操控 SD 总线,指定扇区号,读取扇区。 +- 在能够读取扇区的基础上,解析文件系统,也就是给定文件名,找到文件所在的位置和长度。实际上,文件可能不是连续存储的(被拆成多块放在不同扇区),本库会正确地处理这种情况。 + + + + +# 如何调用本模块 + +RTL 文件夹中的 sd_file_reader.sv 是 SD卡文件读取器的顶层模块,它的定义如下: + +``` +module sd_file_reader #( + parameter FILE_NAME = "example.txt", + parameter [2:0] CLK_DIV = 3'd1, + parameter SIMULATE = 0 +)( + // rstn active-low, 1:working, 0:reset + input wire rstn, + // clock + input wire clk, + // SDcard signals (connect to SDcard), this design do not use sddat1~sddat3. + output wire sdclk, + inout sdcmd, + input wire sddat0, // FPGA only read SDDAT signal but never drive it + // status output (optional for user) + output wire [3:0] card_stat, // show the sdcard initialize status + output wire [1:0] card_type, // 0=UNKNOWN , 1=SDv1 , 2=SDv2 , 3=SDHCv2 + output wire [1:0] filesystem_type, // 0=UNASSIGNED , 1=UNKNOWN , 2=FAT16 , 3=FAT32 + output reg file_found, // 0=file not found, 1=file found + // file content data output (sync with clk) + output reg outen, // when outen=1, a byte of file content is read out from outbyte + output reg [7:0] outbyte // a byte of file content +); +``` + +其中: + +- `FILE_NAME` 指定要读的目标文件名。 +- `CLK_DIV` 是时钟分频系数,它的取值需要根据你提供的 clk 时钟频率来决定(详见代码注释)。 +- `SIMULATE` 是仿真加速选项。**平常要设为 `0` **。只有仿真时才可以设 为 `1` 来加速 SD 卡初始化进度,防止仿真占用过多时间。 +- `clk` 是模块驱动时钟。 +- `rstn` 是复位信号,在开始工作前需要令 `rstn=0` 复位一下,然后令 `rstn=1` 释放。 +- `sdclk` 、 `sdcmd` 、 `sddat0` 是 SD 总线信号,需要连接到 SD 卡。 + - 注意到本模块只用到了 `sddat0` 而没用到 `sddat1~sddat3` ,因为 SD 卡上电会默认运行在 1bit 窄数据总线模式,可以用命令切换到 4bit 宽数据总线模式,我没有做切换,一直只用 `sddat0` 。而 `sddat1~sddat3` 需要弱上拉或强上拉到高电平(避免 SD 卡进入 SPI 总线模式)。 +- `card_type` 指示检测到的 SD 卡的类型:0对应未知、1对应SDv1、2对应SDv2、3对应SDHCv2。 +- `file_system_type` 指示检测到的 SD 卡的文件系统:1对应未知、2对应FAT16、3对应FAT32。 +- `file_found` 指示是否找到目标文件:0代表未找到,1代表找到。 +- 如果找到目标文件,模块会逐个输出目标文件中的所有字节,每输出一个字节,`outen` 上就产生一个高电平脉冲,同时该字节出现在 `outbyte` 上。 + +> **注意**:本库只使用了 sddat0 ,而不使用 sddat1\~3 (也即SD 1-bit总线模式)。在工作时需要将 sddat1\~3 持续拉高(可以在 FPGA 代码里 `assign sddat[3:1] = 3'b111;` ,或者在PCB板上使用上拉电阻),这样 SD 卡才能正常进入 SD 总线模式,否则会进入 SPI 总线模式。 + + + +# 仿真 + +仿真相关的文件都在 SIM 文件夹中,其中: + +- sd_fake.sv :是一个 FPGA 模拟 SD 卡的代码,它模拟了一个具有 FAT32 系统,里面有一个 example.txt 文件的 SD 卡。它来自我的另一个库:[FPGA-SDfake](https://github.com/WangXuan95/FPGA-SDfake) +- tb_sd_file_reader.sv 是仿真顶层,它会调用 sd_file_reader 读取 sd_fake 中的 example.txt 。 +- tb_sd_file_reader_run_iverilog.bat 是运行 iverilog 仿真的脚本。 + +使用 iverilog 进行仿真前,需要安装 iverilog ,见:[iverilog_usage](https://github.com/WangXuan95/WangXuan95/blob/main/iverilog_usage/iverilog_usage.md) + +然后双击 tb_sd_file_reader_run_iverilog.bat 运行仿真,会运行大约几分钟。 + +仿真运行完后,可以打开生成的 dump.vcd 文件查看波形。 + + + +读取文件示例 +=========================== + +example-vivado-readfile 文件夹中包含一个 vivado 工程,它运行在 [Nexys4开发板](http://www.digilent.com.cn/products/product-nexys-4-ddr-artix-7-fpga-trainer-board.html) 上(Nexys4 开发板有 microSD 卡槽,比较方便)。它会从 SD卡根目录中找到文件 example.txt 并读取其全部内容,然后用 **UART** 发送出给PC机。 + +按以下步骤运行该示例: + +1. 准备一张 **microSD卡** 。如果是标准尺寸的SD卡(大SD卡),可以用大卡转 microSD 卡的转接板转接一下。 +1. 确保卡中的文件系统是 **FAT16** 或 **FAT32** 。如果不是,则需要格式化一下。 +2. 在根目录下创建 **example.txt** (文件名大小写不限) , 在文件中随便写入一些内容。 +3. 将卡插入 Nexys4 的卡槽。 +4. 将 Nexys4 的USB口插在PC机上,用 **串口助手** 或 **Putty** 等软件打开对应的串口。 +5. 用 vivado 打开目录 example-vivado-readfile 中的工程,综合并烧录。 +6. 观察到串口打印出文件内容。 +7. 同时,还能看到 Nexys4 上的 LED 灯发生变化,它们指示了SD卡的类型和状态,具体含义见代码。 +8. 按下 Nexys4 上的红色 CPU_RESET 按钮可以重新读取,再次打印出文件内容。 + + + +读取扇区示例 +=========================== + +example-vivado-readsector 文件夹中包含一个 vivado 工程,它运行在 [Nexys4开发板](http://www.digilent.com.cn/products/product-nexys-4-ddr-artix-7-fpga-trainer-board.html) 上。它会从 SD卡中读取扇区0(它在文件系统中往往叫 MBR 扇区),然后用 **UART** 发送出给PC机。 + +按以下步骤运行该示例: + +1. 准备一张 **microSD卡** 。如果是标准尺寸的SD卡(大SD卡),可以用大卡转 microSD 卡的转接板转接一下。 +2. 将 Nexys4 的USB口插在PC机上,用 **串口助手** 打开对应的串口,请选择 “HEX接收” ,以十六进制的形式打印每个字节,这样我们才能看到那些 ASCII 不可打印字符。 +3. 用 vivado 打开目录 example-vivado-readsector 中的工程,综合并烧录。 +4. 观察到串口打印出扇区0的内容。 +5. 同时,还能看到 Nexys4 上的 LED 灯发生变化,它们指示了SD卡的类型和状态,具体含义见代码注释。 +6. 按下 Nexys4 上的红色 CPU_RESET 按钮可以重新读取,再次打印出扇区内容。 + + + +FPGA SDcard File Reader +=========================== + +FPGA-based SDcard file reader + +* **Function**: FPGA acts as **SD-host**, specifying the file name to read the file content; or specifying the sector number to read the sector content. +* **Performance**: Use SD bus instead of SPI bus. Read faster. +* **Strong compatibility**: automatically adapt to **SDcard version**, automatically adapt to **FAT16/FAT32 file system**. +* pure RTL implement, easy to simulate and transplant. + +| | SDv1.1 card | SDv2 card | SDHCv2 card | +| :-------------------- | :----------------: | :----------------: | :----------------: | +| **Read Sector** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| **Read File (FAT16)** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| **Read File (FAT32)** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | + + + +# Background + +## SD bus + +The SDcard is connected to the SD-host (such as a card adaptor) using the SD bus. The signals of the SD bus include: + +| Signal name | Direction | +| ------------------------------ | ------------------------------------------------------------ | +| sdclk | host→card | +| sdcmd | host→card when request command, card→host when respond command | +| sddat0, sddat1, sddat2, sddat3 | host→card, when write data, card→host when read data | + +The pin definitions of these signals on SDcard and microSDcard are as follows (SDcard and microSDcard have no difference in function except for the shape and appearance). + +| ![pin](./figures/pin.png) | +| :----------------------------------------------------------: | +| Figure : pin defination of SDcard (left) and microSDcard (right) | + +## File System + +The SDcard is just a large linear data storage space, divided into multiple sectors, each sector contains 512 bytes, the address range of sector 0 is 0x00000000\~0x000001FF, and the address range of sector 1 is 0x00000200\~0x000003FF, and so on…. The underlying read and write operations are performed in unit of sectors. In order to organize disk partitions and files in this linear storage space, people stipulate complex data structures: file system. The most commonly used file systems for SDcard are FAT16 and FAT32. + +In order to read file data from SDcard, this library is divided into two functional modules: + +- Manipulate the SD bus according to the SD bus standard, specify the sector number and read the sector. +- On the basis of being able to read sectors, parse the file system, that is, given the file name, to find the location and length of the file. In fact, the file may not be stored contiguously (that is, split into multiple blocks in different sectors), my code will handle this case correctly. + + + + +# How to use this module + +sd_file_reader.sv in the [RTL](./RTL) folder is the top-level module of the SD card file reader, and it is defined as follows: + +``` +module sd_file_reader #( + parameter FILE_NAME = "example.txt", + parameter [2:0] CLK_DIV = 3'd1, + parameter SIMULATE = 0 +)( + // rstn active-low, 1:working, 0:reset + input wire rstn, + // clock + input wire clk, + // SDcard signals (connect to SDcard), this design do not use sddat1~sddat3. + output wire sdclk, + inout sdcmd, + input wire sddat0, // FPGA only read SDDAT signal but never drive it + // status output (optional for user) + output wire [3:0] card_stat, // show the sdcard initialize status + output wire [1:0] card_type, // 0=UNKNOWN , 1=SDv1 , 2=SDv2 , 3=SDHCv2 + output wire [1:0] filesystem_type, // 0=UNASSIGNED , 1=UNKNOWN , 2=FAT16 , 3=FAT32 + output reg file_found, // 0=file not found, 1=file found + // file content data output (sync with clk) + output reg outen, // when outen=1, a byte of file content is read out from outbyte + output reg [7:0] outbyte // a byte of file content +); +``` + +where: + +- `FILE_NAME` specifies the name of the target file to read. +- `CLK_DIV` is the clock frequency division factor, its value needs to be determined according to the `clk` frequency you provide (see code comments for details). +- `SIMULATE` is the simulation speed-up option. **Usually set to `0`**. It can be set to `1` only during simulation to speed up the SD card initialization progress and prevent the simulation from taking too much time. +- `clk` is the module driving clock. +- `rstn` is the reset signal, you need to set `rstn=0` before starting to work, and then set `rstn=1` to release it. +- `sdclk` , `sdcmd` , `sddat0` are SD bus signals, should connect to SDcard. + - Note that this module only uses `sddat0` and does not use `sddat1~sddat3`, because the SDcard will run in 1bit narrow data bus mode by default when powered on, and can be switched to 4bit wide data bus mode with the command, I did not switch it, always use only `sddat0`. And `sddat1~sddat3` can be left unconnected. +- `card_type` will output the detected SDcard type: 0 corresponds to unknown, 1 corresponds to SDv1, 2 corresponds to SDv2, 3 corresponds to SDHCv2. +- `file_system_type` will output the detected file system of SDcard: 1 corresponds to unknown, 2 corresponds to FAT16, 3 corresponds to FAT32. +- `file_found` will output whether the target file was found: 0 means not found, 1 means found. +- If the target file is found, the module will output all the bytes in the file. For each output byte, a high-level pulse will be generated on `outen`, and the byte will appear on `outbyte`. + +> **Note**: This repo only uses sddat0 (i.e. SD 1-bit bus mode) instead of sddat1\~3. When working, you need to continuously pull sddat1\~3 high (you can write `assign sddat[3:1] = 3'b111;` in the FPGA code, or use pull-up resistors on the PCB). This is to ensure that the SD card can enter SD bus mode normally, otherwise it will enter SPI bus mode. + + + +# RTL Simulation + +Simulation related files are in the [SIM](./SIM) folder, where: + +- sd_fake.sv is a code for FPGA to imitate an SDcard. In this simulation program, it imitates an SDcard with FAT32 system and has an file example.txt inside. It comes from another repository of mine: [FPGA-SDfake](https://github.com/WangXuan95/FPGA-SDfake) +- tb_sd_file_reader.sv is the top level of the simulation, it will call sd_file_reader.sv to read the file in sd_fake.sv . +- tb_sd_file_reader_run_iverilog.bat is a command script to run iverilog simulations. + +Before simulate using iverilog, you need to install iverilog , see: [iverilog_usage](https://github.com/WangXuan95/WangXuan95/blob/main/iverilog_usage/iverilog_usage.md) + +Then double-click tb_sd_file_reader_run_iverilog.bat to run simulation, which will run for several minutes. + +After the simulation runs, you can open the generated dump.vcd file to view the waveform. + + + +FPGA Demo: Read File +=========================== + +The [example-vivado-readfile](./example-vivado-readfile) folder contains a vivado project, which runs on the [Nexys4 development board](http://www.digilent.com.cn/products/product-nexys-4-ddr-artix-7-fpga- trainer-board.html) (it has a microSDcard slot,). It will find the file example.txt from the root directory of the SDcard and read its entire content, and then send it to PC via **UART**. + +Run the demo as follows: + +1. Prepare a **microSDcard** of **FAT16** or **FAT32**. If it is not **FAT16** or **FAT32**, you need to format it. +2. Create **example.txt** in the root directory (the file name is not limited in case), and write some content in the file. +3. Insert the SDcard into the card slot of Nexys4. +4. Plug the USB port of Nexys4 into PC, and open the corresponding serial port with software such as **Serial Assistant**, **Putty**, **HyperTerminal** or **minicom**. +5. Open the project in the directory [example-vivado-readfile](./example-vivado-readfile) with vivado, synthesize and program it. +6. Observe that the serial port prints the contents of the file. +7. Simutinously, you can see that the LEDs on the Nexys4 change, they indicate the type and status of the SDcard, see the code for the specific meaning. +8. Press the red CPU\_RESET button on Nexys4 to re-read and print out the file content again. + + + +FPGA Demo: Read Sector +=========================== + +The [example-vivado-readsector](./example-vivado-readsector) folder contains a vivado project, which runs on the [Nexys4 development board](http://www.digilent.com.cn/products/product-nexys-4-ddr-artix-7-fpga- trainer-board.html), it will read sector 0 from the SD card (it is often called the MBR sector in file systems) and send it to PC via **UART**. + +Run the demo as follows: + +1. Prepare a **microSDcard**. Plug it into the card slot of Nexys4. +2. Plug the USB port of Nexys4 into PC, use **Serial Assistant** to open the corresponding serial port, please select "HEX Receive Mode", print each byte in hexadecimal form, so that we can see those ASCII non-printable characters. +3. Use vivado to open the project in the directory [example-vivado-readsector](./example-vivado-readsector) , synthesize and program it. +4. Observe that the serial port prints the contents of sector 0. +5. At the same time, you can also see that the LEDs on the Nexys4 change, they indicate the type and status of the SDcard, see the code comments for the specific meaning. +6. Press the red CPU\_RESET button on Nexys4 to re-read and print out the sector content again. + diff --git a/RTL/sd_file_reader.sv b/RTL/sd_file_reader.sv new file mode 100644 index 0000000..6aa1fcf --- /dev/null +++ b/RTL/sd_file_reader.sv @@ -0,0 +1,531 @@ + +//-------------------------------------------------------------------------------------------------------- +// Module : sd_file_reader +// Type : synthesizable, IP's top +// Standard: SystemVerilog 2005 (IEEE1800-2005) +// Function: A SD-host. +// Specify a filename, sd_file_reader will read out file content +// Compatibility: CardType : SDv1.1 , SDv2 or SDHCv2 +// FileSystem : FAT16 or FAT32 +//-------------------------------------------------------------------------------------------------------- + +module sd_file_reader #( + parameter FILE_NAME = "example.txt", // file to read, ignore Upper and Lower Case + // For example, if you want to read a file named HeLLo123.txt in the SD card, + // this parameter can be hello123.TXT, HELLO123.txt or HEllo123.Txt + parameter [2:0] CLK_DIV = 3'd1, // when clk = 0~ 25MHz , set CLK_DIV = 3'd0, + // when clk = 25~ 50MHz , set CLK_DIV = 3'd1, + // when clk = 50~100MHz , set CLK_DIV = 3'd2, + // when clk = 100~200MHz , set CLK_DIV = 3'd3, + // ...... + parameter SIMULATE = 0 +)( + // rstn active-low, 1:working, 0:reset + input wire rstn, + // clock + input wire clk, + // SDcard signals (connect to SDcard), this design do not use sddat1~sddat3. + output wire sdclk, + inout sdcmd, + input wire sddat0, // FPGA only read SDDAT signal but never drive it + // status output (optional for user) + output wire [3:0] card_stat, // show the sdcard initialize status + output wire [1:0] card_type, // 0=UNKNOWN , 1=SDv1 , 2=SDv2 , 3=SDHCv2 + output wire [1:0] filesystem_type, // 0=UNASSIGNED , 1=UNKNOWN , 2=FAT16 , 3=FAT32 + output reg file_found, // 0=file not found, 1=file found + // file content data output (sync with clk) + output reg outen, // when outen=1, a byte of file content is read out from outbyte + output reg [7:0] outbyte // a byte of file content +); + +initial file_found = 1'b0; +initial {outen,outbyte} = '0; + +function automatic logic [7:0] toUpperCase(input [7:0] in); + return (in>=8'h61 && in<=8'h7A) ? in&8'b11011111 : in; +endfunction + +localparam TARGET_FNAME_LEN = ($bits(FILE_NAME)/8); + +wire [$bits(FILE_NAME)-1:0] TARGET_FNAME = FILE_NAME; +reg [$bits(FILE_NAME)-1:0] TARGET_FNAME_UPPER; +always_comb + for(int ii=0; ii0) begin // FAT16 case + filesystem_parsed = FAT16; + end else if(sector_content['h56]==8'h32) begin // FAT32 case + filesystem_parsed = FAT32; + sectors_per_fat = {sector_content['h27],sector_content['h26],sector_content['h25],sector_content['h24]}; + root_cluster = {sector_content['h2F],sector_content['h2E],sector_content['h2D],sector_content['h2C]}; + end else begin // Unknown FileSystem + filesystem_parsed = UNKNOWN; + end +end + + + +//---------------------------------------------------------------------------------------------------------------------- +// main FSM +//---------------------------------------------------------------------------------------------------------------------- +always @ (posedge clk or negedge rstn) + if(~rstn) begin + read_start <= 1'b0; + read_sector_no <= 0; + filesystem_state <= RESET; + filesystem <= UNASSIGNED; + search_fat <= 1'b0; + cluster_size <= 8'h0; + first_fat_sector_no <= 0; + first_data_sector_no <= 0; + curr_cluster <= 0; + cluster_sector_offset <= 8'h0; + rootdir_sector <= 0; + rootdir_sectorcount <= 16'h0; + end else begin + cluster_size_t = cluster_size; + first_fat_sector_no_t = first_fat_sector_no; + first_data_sector_no_t = first_data_sector_no; + curr_cluster_t = curr_cluster; + cluster_sector_offset_t = cluster_sector_offset; + rootdir_sector_t = rootdir_sector; + rootdir_sectorcount_t = rootdir_sectorcount; + + read_start <= 1'b0; + if(read_done) begin + case(filesystem_state) + SEARCH_MBR : if(is_boot_sector) begin + filesystem_state <= SEARCH_DBR; + if(~is_dbr) read_sector_no <= dbr_sector_no; + end else begin + read_sector_no <= read_sector_no + 1; + end + SEARCH_DBR : if(is_boot_sector && is_dbr ) begin + if(bytes_per_sector!=16'd512) begin + filesystem_state <= DONE; + end else begin + filesystem <= filesystem_parsed; + if(filesystem_parsed==FAT16) begin + cluster_size_t = sector_per_cluster; + first_fat_sector_no_t = read_sector_no + resv_sectors; + + rootdir_sectorcount_t = rootdir_itemcount / (16'd512/16'd32); + rootdir_sector_t = first_fat_sector_no_t + sectors_per_fat * number_of_fat; + first_data_sector_no_t= rootdir_sector_t + rootdir_sectorcount_t - cluster_size_t*2; + + cluster_sector_offset_t = 8'h0; + read_sector_no <= rootdir_sector_t + cluster_sector_offset_t; + filesystem_state <= LS_ROOT_FAT16; + end else if(filesystem_parsed==FAT32) begin + cluster_size_t = sector_per_cluster; + first_fat_sector_no_t = read_sector_no + resv_sectors; + + first_data_sector_no_t= first_fat_sector_no_t + sectors_per_fat * number_of_fat - cluster_size_t * 2; + + curr_cluster_t = root_cluster; + cluster_sector_offset_t = 8'h0; + read_sector_no <= first_data_sector_no_t + cluster_size_t * curr_cluster_t + cluster_sector_offset_t; + filesystem_state <= LS_ROOT_FAT32; + end else begin + filesystem_state <= DONE; + end + end + end + LS_ROOT_FAT16 : if(file_found) begin + curr_cluster_t = file_cluster; + cluster_sector_offset_t = 8'h0; + read_sector_no <= first_data_sector_no_t + cluster_size_t * curr_cluster_t + cluster_sector_offset_t; + filesystem_state <= READ_A_FILE; + end else if(cluster_sector_offset_t=16'hFFF0 || target_cluster_fat16<16'h2) begin + filesystem_state <= DONE; // read to the end of file, done + end else begin + curr_cluster_t = {16'h0,target_cluster_fat16}; + read_sector_no <= first_data_sector_no_t + cluster_size_t * curr_cluster_t + cluster_sector_offset_t; + end + end else begin + if(target_cluster=='h0FFF_FFFF || target_cluster=='h0FFF_FFF8 || target_cluster=='hFFFF_FFFF || target_cluster<2) begin + filesystem_state <= DONE; // read to the end of file, done + end else begin + curr_cluster_t = target_cluster; + read_sector_no <= first_data_sector_no_t + cluster_size_t * curr_cluster_t + cluster_sector_offset_t; + end + end + end + endcase + end else begin + case(filesystem_state) + RESET : filesystem_state <= SEARCH_MBR; + SEARCH_MBR : read_start <= 1'b1; + SEARCH_DBR : read_start <= 1'b1; + LS_ROOT_FAT16 : read_start <= 1'b1; + LS_ROOT_FAT32 : read_start <= 1'b1; + READ_A_FILE : read_start <= 1'b1; + DONE : $finish; // only for finish simulation, will be ignore when synthesize + endcase + end + + cluster_size <= cluster_size_t; + first_fat_sector_no <= first_fat_sector_no_t; + first_data_sector_no <= first_data_sector_no_t; + curr_cluster <= curr_cluster_t; + cluster_sector_offset <= cluster_sector_offset_t; + rootdir_sector <= rootdir_sector_t; + rootdir_sectorcount <= rootdir_sectorcount_t; + end + + + +//---------------------------------------------------------------------------------------------------------------------- +// capture data in FAT table +//---------------------------------------------------------------------------------------------------------------------- +always @ (posedge clk or negedge rstn) begin + if(~rstn) begin + target_cluster <= 0; + target_cluster_fat16 <= 16'h0; + end else begin + if(search_fat && rvalid) begin + if(filesystem==FAT16) begin + if(raddr[8:1]==curr_cluster_fat_offset_fat16) + target_cluster_fat16[8*raddr[ 0] +: 8] <= rdata; + end else if(filesystem==FAT32) begin + if(raddr[8:2]==curr_cluster_fat_offset) + target_cluster[8*raddr[1:0] +: 8] <= rdata; + end + end + end +end + + +sd_reader #( + .CLK_DIV ( CLK_DIV ), + .SIMULATE ( SIMULATE ) +) sd_reader_i ( + .rstn ( rstn ), + .clk ( clk ), + .sdclk ( sdclk ), + .sdcmd ( sdcmd ), + .sddat0 ( sddat0 ), + .card_type ( card_type ), + .card_stat ( card_stat ), + .rstart ( read_start ), + .rsector ( read_sector_no ), + .rbusy ( ), + .rdone ( read_done ), + .outen ( rvalid ), + .outaddr ( raddr ), + .outbyte ( rdata ) +); + + + +//---------------------------------------------------------------------------------------------------------------------- +// parse root dir +//---------------------------------------------------------------------------------------------------------------------- +reg fready = 1'b0; // a file is find when fready = 1 +reg [ 7:0] fnamelen = '0; +reg [15:0] fcluster = '0; +reg [31:0] fsize = 0; +reg [ 7:0] fname [52]; +reg [ 7:0] file_name [52]; +reg isshort=1'b0, islongok=1'b0, islong=1'b0, longvalid=1'b0; +reg isshort_t , islongok_t , islong_t , longvalid_t ; +reg [ 5:0] longno = 6'h0 , longno_t; +reg [ 7:0] lastchar = 8'h0; +reg [ 7:0] fdtnamelen = 8'h0 , fdtnamelen_t; +reg [ 7:0] sdtnamelen = 8'h0 , sdtnamelen_t; +reg [ 7:0] file_namelen = 8'h0; +reg [15:0] file_1st_cluster = 16'h0 , file_1st_cluster_t; +reg [31:0] file_1st_size = 0 , file_1st_size_t; + +initial for(int i=0;i<52;i++) begin file_name[i]=8'h0; fname[i]=8'h0; end + +always @ (posedge clk or negedge rstn) begin + if(~rstn) begin + fready<=1'b0; fnamelen<=8'h0; file_namelen<=8'h0; + fcluster<=16'h0; fsize<=0; + for(int i=0;i<52;i++) begin file_name[i]<=8'h0; fname[i]<=8'h0; end + + {isshort, islongok, islong, longvalid} <= 4'b0000; + + longno <= 6'h0; + lastchar <= 8'h0; + fdtnamelen <= 8'h0; + sdtnamelen <= 8'h0; + file_1st_cluster <= 16'h0; + file_1st_size <= 0; + end else begin + {isshort_t, islongok_t, islong_t, longvalid_t} = {isshort, islongok, islong, longvalid}; + longno_t = longno; + fdtnamelen_t = fdtnamelen; + sdtnamelen_t = sdtnamelen; + file_1st_cluster_t = file_1st_cluster; + file_1st_size_t = file_1st_size; + + fready<=1'b0; fnamelen<=8'h0; + for(int i=0;i<52;i++) fname[i]<=8'h0; + fcluster<=16'h0; fsize<=0; + + if( rvalid && (filesystem_state==LS_ROOT_FAT16||filesystem_state==LS_ROOT_FAT32) && ~search_fat ) begin + case(raddr[4:0]) + 5'h1A : file_1st_cluster_t[ 0+:8] = rdata; + 5'h1B : file_1st_cluster_t[ 8+:8] = rdata; + 5'h1C : file_1st_size_t[ 0+:8] = rdata; + 5'h1D : file_1st_size_t[ 8+:8] = rdata; + 5'h1E : file_1st_size_t[16+:8] = rdata; + 5'h1F : file_1st_size_t[24+:8] = rdata; + endcase + + if(raddr[4:0]==5'h0) begin + {islongok_t, isshort_t} = 2'b00; + fdtnamelen_t = 8'h0; sdtnamelen_t=8'h0; + + if(rdata!=8'hE5 && rdata!=8'h2E && rdata!=8'h00) begin + if(islong_t && longno_t==6'h1) + islongok_t = 1'b1; + else + isshort_t = 1'b1; + end + + if(rdata[7]==1'b0 && ~islongok_t) begin + if(rdata[6]) begin + {islong_t,longvalid_t} = 2'b11; + longno_t = rdata[5:0]; + end else if(islong_t) begin + if(longno_t>6'h1 && (rdata[5:0]+6'h1==longno_t) ) begin + islong_t = 1'b1; + longno_t = rdata[5:0]; + end else begin + islong_t = 1'b0; + end + end else + islong_t = 1'b0; + end else + islong_t = 1'b0; + end else if(raddr[4:0]==5'hB) begin + if(rdata!=8'h0F) + islong_t = 1'b0; + if(rdata!=8'h20) + {isshort_t, islongok_t} = 2'b00; + end else if(raddr[4:0]==5'h1F) begin + if(islongok_t && longvalid_t || isshort_t) begin + fready <= 1'b1; + fnamelen <= file_namelen; + for(int i=0;i<52;i++) fname[i] <= (i5'h0&&raddr[4:0]<5'hB || raddr[4:0]>=5'hE&&raddr[4:0]<5'h1A || raddr[4:0]>=5'h1C)begin + if(raddr[4:0]<5'hB ? raddr[0] : ~raddr[0]) begin + lastchar <= rdata; + fdtnamelen_t++; + end else begin + //automatic logic [15:0] unicode = {rdata,lastchar}; + if({rdata,lastchar} == 16'h0000) begin + file_namelen <= fdtnamelen_t-8'd1 + (longno_t-8'd1)*8'd13; + end else if({rdata,lastchar} != 16'hFFFF) begin + if(rdata == 8'h0) begin + file_name[fdtnamelen_t-8'd1+(longno_t-8'd1)*8'd13] <= (lastchar>=8'h61 && lastchar<=8'h7A) ? lastchar&8'b11011111 : lastchar; + end else begin + longvalid_t = 1'b0; + end + end + end + end + end + + if(isshort_t) begin + if(raddr[4:0]<5'h8) begin + if(rdata!=8'h20) begin + file_name[sdtnamelen_t] <= rdata; + sdtnamelen_t++; + end + end else if(raddr[4:0]<5'hB) begin + if(raddr[4:0]==5'h8) begin + file_name[sdtnamelen_t] <= 8'h2E; + sdtnamelen_t++; + end + if(rdata!=8'h20) begin + file_name[sdtnamelen_t] <= rdata; + sdtnamelen_t++; + end + end else if(raddr[4:0]==5'hB) begin + file_namelen <= sdtnamelen_t; + end + end + + end + + {isshort, islongok, islong, longvalid} <= {isshort_t, islongok_t, islong_t, longvalid_t}; + longno <= longno_t; + fdtnamelen <= fdtnamelen_t; + sdtnamelen <= sdtnamelen_t; + file_1st_cluster <= file_1st_cluster_t; + file_1st_size <= file_1st_size_t; + end +end + + + +//---------------------------------------------------------------------------------------------------------------------- +// compare Target filename with Parsed filename +//---------------------------------------------------------------------------------------------------------------------- +always @ (posedge clk or negedge rstn) + if(~rstn) begin + file_found <= 1'b0; + file_cluster <= 0; + file_size <= 0; + end else begin + if(fready && fnamelen==TARGET_FNAME_LEN) begin + file_found <= 1'b1; + file_cluster <= fcluster; + file_size <= fsize; + for(int ii=0; ii= 512*8-1) begin + sddat_stat <= RTAIL; + ridx <= 0; + end else begin + ridx <= ridx + 1; + end + end + RTAIL : begin + if(ridx >= 8*8-1) + sddat_stat <= RDONE; + ridx <= ridx + 1; + end + endcase + end + end + + +endmodule + diff --git a/RTL/sdcmd_ctrl.sv b/RTL/sdcmd_ctrl.sv new file mode 100644 index 0000000..760cb58 --- /dev/null +++ b/RTL/sdcmd_ctrl.sv @@ -0,0 +1,133 @@ + +//-------------------------------------------------------------------------------------------------------- +// Module : sdcmd_ctrl +// Type : synthesizable, IP's sub module +// Standard: SystemVerilog 2005 (IEEE1800-2005) +// Function: sdcmd signal control, +// instantiated by sd_reader +//-------------------------------------------------------------------------------------------------------- + +module sdcmd_ctrl ( + input wire rstn, + input wire clk, + // SDcard signals (sdclk and sdcmd) + output reg sdclk, + inout sdcmd, + // config clk freq + input wire [15:0] clkdiv, + // user input signal + input wire start, + input wire [15:0] precnt, + input wire [ 5:0] cmd, + input wire [31:0] arg, + // user output signal + output reg busy, + output reg done, + output reg timeout, + output reg syntaxe, + output wire [31:0] resparg +); + +initial {busy, done, timeout, syntaxe} = '0; +initial sdclk = '0; + +localparam [7:0] TIMEOUT = 8'd250; + +reg sdcmdoe = 1'b0; +reg sdcmdout = 1'b1; + +// sdcmd tri-state driver +assign sdcmd = sdcmdoe ? sdcmdout : 1'bz; +wire sdcmdin = sdcmdoe ? 1'b1 : sdcmd; + +function automatic logic [6:0] CalcCrc7(input logic [6:0] crc, input logic inbit); + return {crc[5:0],crc[6]^inbit} ^ {3'b0,crc[6]^inbit,3'b0}; +endfunction + +reg [ 5:0] req_cmd = '0; // request[45:40] +reg [31:0] req_arg = '0; // request[39: 8] +reg [ 6:0] req_crc = '0; // request[ 7: 1] +wire [51:0] request = {6'b111101, req_cmd, req_arg, req_crc, 1'b1}; + +struct packed { + logic st; + logic [ 5:0] cmd; + logic [31:0] arg; +} response = '0; + +assign resparg = response.arg; + +reg [17:0] clkdivr = '1; +reg [17:0] clkcnt = '0; +reg [15:0] cnt1 = '0; +reg [ 5:0] cnt2 = '1; +reg [ 7:0] cnt3 = '0; +reg [ 7:0] cnt4 = '1; + + +always @ (posedge clk or negedge rstn) + if(~rstn) begin + {busy, done, timeout, syntaxe} <= '0; + sdclk <= 1'b0; + {sdcmdoe, sdcmdout} <= 2'b01; + {req_cmd, req_arg, req_crc} <= '0; + response <= '0; + clkdivr <= '1; + clkcnt <= '0; + cnt1 <= '0; + cnt2 <= '1; + cnt3 <= '0; + cnt4 <= '1; + end else begin + {done, timeout, syntaxe} <= '0; + + clkcnt <= ( clkcnt < {clkdivr[16:0],1'b1} ) ? clkcnt+18'd1 : '0; + + if (clkcnt == '0) + clkdivr <= {2'h0, clkdiv} + 18'd1; + + if(clkcnt == clkdivr) + sdclk <= 1'b0; + else if(clkcnt == {clkdivr[16:0],1'b1} ) + sdclk <= 1'b1; + + if(~busy) begin + if(start) busy <= '1; + req_cmd <= cmd; + req_arg <= arg; + req_crc <= '0; + cnt1 <= precnt; + cnt2 <= 6'd51; + cnt3 <= TIMEOUT; + cnt4 <= 8'd134; + end else if(done) begin + busy <= '0; + end else if( clkcnt == clkdivr) begin + {sdcmdoe, sdcmdout} <= 2'b01; + if (cnt1 != '0) begin + cnt1 <= cnt1 - 16'd1; + end else if(cnt2 != '1) begin + cnt2 <= cnt2 - 6'd1; + {sdcmdoe, sdcmdout} <= {1'b1, request[cnt2]}; + if(cnt2>=8 && cnt2<48) req_crc <= CalcCrc7(req_crc, request[cnt2]); + end + end else if( clkcnt == {clkdivr[16:0],1'b1} && cnt1=='0 && cnt2=='1 ) begin + if(cnt3 != '0) begin + cnt3 <= cnt3 - 8'd1; + if(~sdcmdin) + cnt3 <= '0; + else if(cnt3 == 8'd1) + {done, timeout, syntaxe} <= 3'b110; + end else if(cnt4 != '1) begin + cnt4 <= cnt4 - 8'd1; + if(cnt4 >= 8'd96) + response <= {response[37:0], sdcmdin}; + if(cnt4 == '0) begin + {done, timeout} <= 2'b10; + syntaxe <= response.st || ((response.cmd!=req_cmd) && (response.cmd!='1) && (response.cmd!='0)); + end + end + end + end + +endmodule diff --git a/SIM/sd_fake.sv b/SIM/sd_fake.sv new file mode 100644 index 0000000..96c2fe5 --- /dev/null +++ b/SIM/sd_fake.sv @@ -0,0 +1,580 @@ + +//-------------------------------------------------------------------------------------------------------- +// Module : sd_fake +// Type : synthesizable, IP's top +// Standard: SystemVerilog 2005 (IEEE1800-2005) +// Function: Imitate a SDHCv2 Read-Only SD card +//-------------------------------------------------------------------------------------------------------- + +module sd_fake ( + input wire rstn_async, + // SD-card signals, connect to a SD-host, such as a SDcard Reader + input wire sdclk, + inout sdcmd, + output wire [ 3:0] sddat, + // data read interface, connect to a RAM which contains SD-card's data. + output reg rdreq, + output reg [39:0] rdaddr, + input wire [15:0] rddata, + // show status (optional) + output wire [ 7:0] show_status_bits, + // show parsed request command on sdcmd (optional) + output reg show_sdcmd_en, + output reg [ 5:0] show_sdcmd_cmd, + output reg [31:0] show_sdcmd_arg +); + +initial rdreq = '0; +initial rdaddr = '0; + +initial show_sdcmd_en = '0; +initial show_sdcmd_cmd = '0; +initial show_sdcmd_arg = '0; + + +// generate reset sync with posedge of sdclk +reg rstn_sdclk_p = '0; +reg [1:0] rstn_sdclk_p_l = '0; +always @ (posedge sdclk or negedge rstn_async) + if(~rstn_async) + {rstn_sdclk_p, rstn_sdclk_p_l} <= '0; + else + {rstn_sdclk_p, rstn_sdclk_p_l} <= {rstn_sdclk_p_l, 1'b1}; + + +// generate reset sync with negedge of sdclk +reg rstn_sdclk_n = '0; +reg [1:0] rstn_sdclk_n_l = '0; +always @ (negedge sdclk or negedge rstn_async) + if(~rstn_async) + {rstn_sdclk_n, rstn_sdclk_n_l} <= '0; + else + {rstn_sdclk_n, rstn_sdclk_n_l} <= {rstn_sdclk_n_l, 1'b1}; + + + +reg sdcmdoe = 1'b0; +reg sdcmdout = 1'b1; +reg sddatoe = 1'b0; +reg [3:0] sddatout = '1; + +assign sdcmd = sdcmdoe ? sdcmdout : 1'bz; +assign sddat = sddatoe ? sddatout : 4'bz; + +function automatic logic [ 6:0] CalcCrcCMD(input [ 6:0] crc, input inbit); + return {crc[5:0],crc[6]^inbit} ^ {3'b0,crc[6]^inbit,3'b0}; +endfunction + +function automatic logic [15:0] CalcCrcDAT(input [15:0] crc, input inbit); + return {crc[14:0],crc[15]^inbit} ^ {3'b0,crc[15]^inbit,6'b0,crc[15]^inbit,5'b0}; +endfunction + +localparam BLOCK_SIZE = 512; // 512B per block +localparam [ 15:0] RCA_REG = 16'h0013; + +localparam [ 31:0] OCR_REG = {1'b1,1'b1,6'b0,9'h1ff,7'b0,1'b0,7'b0}; // not busy, CCS=1(SDHC card), all voltage, not dual-voltage card + +localparam [119:0] CID_REG = 120'h02544d53_41303847_14394a67_c700e4; + +localparam [119:0] CSD_REG = 120'h400e0032_50590000_39b73f80_000030; // 25MHz, SD-ROM card + // 120'h400e0032_5b590000_39b77f80_0a4000; // 25MHz, Normal card + +localparam [ 64:0] SCR_REG = 64'h0005_0000_00000000; + // 64'h0201_0000_00000000; // SD-ROM card, disable 4-bit bus mode + // 64'h0205_0000_00000000; // SD-ROM card, enable 4-bit bus mode + // 64'h0231_0000_00000000; // Normal card, disable 4-bit bus mode + // 64'h0235_0000_00000000; // Normal card, enable 4-bit bus mode + +reg last_is_acmd=1'b0; + +enum logic [1:0] {WAITINGCMD, LOADRESP, RESPING} respstate = WAITINGCMD; + +struct packed{ + logic [ 3:0] pre_st; + logic [ 5:0] cmd; + logic [31:0] arg; + logic [ 6:0] crc; + logic stop; +} request = '0; + +typedef enum logic [3:0] {IDLE, READY, IDENT, STBY, TRAN, DATA, RCV, PRG, DIS} current_state_t; + +struct packed{ + logic out_of_range; + logic address_error; + logic block_len_error; + logic erase_seq_error; + logic erase_param; + logic wp_violation; + logic card_is_locked; + logic lock_unlock_failed; + logic com_crc_error; + logic illegal_command; + logic card_ecc_failed; + logic cc_error; + logic error; + logic [1:0] rsvd1; // reserved + logic csd_overwrite; + logic wp_erase_skip; + logic card_ecc_disabled; + logic erase_reset; + current_state_t current_state; + logic ready_for_data; + logic [1:0] rsvd2; + logic app_cmd; + logic rsvd3; + logic ake_seq_error; + logic [2:0] rsvd4; +} cardstatus = '0; +wire [15:0] cardstatus_short = {cardstatus[23:22], cardstatus[19], cardstatus[12:0]}; // for R6 (CMD3) + +localparam HIGHZLEN = 1; +localparam WAITLEN = HIGHZLEN + 3; +reg [ 5:0] cmd='0; +reg [119:0] arg='0; +reg [ 6:0] crc='0; + +reg response_end = 1'b0; +reg valid='0, dummycrc='0; +int idx=0, arglen=0; + +task automatic response_init(input _valid, input _dummycrc, input [5:0] _cmd, input int _arglen, input [119:0] _arg); + cmd = _cmd; + arg = _arg; + crc = '0; + valid = _valid; + dummycrc = _dummycrc; + idx = 0; + arglen = _arglen; + response_end = 1'b0; +endtask + +task automatic response_yield; + response_end = 1'b0; + if ( ~valid) begin + sdcmdoe = 0; + sdcmdout = 1; + response_end = 1'b1; + end else if(idx0; i--) cmdcrcval = CalcCrcCMD(cmdcrcval, request[i]); +end + +always @ (posedge sdclk or negedge rstn_sdclk_p) + if(~rstn_sdclk_p) begin + respstate <= WAITINGCMD; + request <= '1; + end else begin + case(respstate) + WAITINGCMD:begin + if(request.pre_st==4'b1101 && request.stop) begin + if(cmdcrcval==7'd0) + respstate <= LOADRESP; + else + request <= '1; + end else begin + request <= {request[48:0],sdcmd}; + end + end + LOADRESP : respstate <= RESPING; + RESPING : if(response_end) begin + respstate <= WAITINGCMD; + request <= '1; + end + endcase + end + +always @ (negedge sdclk or negedge rstn_sdclk_n) + if(~rstn_sdclk_n) begin + response_init( 0, 0, 0, 0, 0 ); + data_response_stop; + response_yield; + data_response_yield; + last_is_acmd <= 1'b0; + cardstatus = '0; + widebus = 0; + cmd6_invalid <= 6'h0; + end else begin + if(respstate==LOADRESP) begin + last_is_acmd <= 1'b0; + cardstatus.app_cmd = 1'b0; + cardstatus.block_len_error = 1'b0; + case(request.cmd) + 0 : begin // GO_IDLE_STATE + response_init( 0, 0 , 0 , 0 , 0 ); // there is NO RESPONSE for CMD0 + data_response_stop; + response_yield; + data_response_yield; + last_is_acmd <= 1'b0; + cardstatus = '0; + cardstatus.ready_for_data = 1'b1; + widebus = 0; + cmd6_invalid <= 6'h0; + end + 2 : begin // ALL_SEND_CID + response_init( 1, 0 , 6'b000000 , 120 , CID_REG ); // R2 TODO: why cmd=000000 instead of 111111 ??? + cardstatus.current_state = IDENT; + cardstatus.illegal_command = 1'b0; + end + 3 : begin // SEND_RELATIVE_ADDR(send RCA) + response_init( 1, 0 , request.cmd , 32 , {RCA_REG,cardstatus_short} ); // R6 + cardstatus.current_state = STBY; + cardstatus.illegal_command = 1'b0; + end + 4 : if(request.arg[15:0] == 16'h0) begin // SET_DSR + response_init( 0, 0 , 0 , 0 , 0 ); // there is NO RESPONSE for CMD4 + cardstatus.illegal_command = 1'b0; + end + 6 : if(last_is_acmd && cardstatus.current_state==TRAN) begin // SET_BUS_WIDTH + cardstatus.app_cmd = 1'b1; + response_init( 1, 0 , request.cmd , 32 , cardstatus ); + widebus = request.arg[1]; + cardstatus.illegal_command = 1'b0; + end else if(cardstatus.current_state==TRAN) begin // SWITCH_FUNC + response_init( 1, 0 , request.cmd , 32 , cardstatus ); + cmd6_invalid[0] <= ( request.arg[0*4+:4]!=4'h0 && request.arg[0*4+:4]!=4'hf ); + cmd6_invalid[1] <= ( request.arg[1*4+:4]!=4'h0 && request.arg[1*4+:4]!=4'hf ); + cmd6_invalid[2] <= ( request.arg[2*4+:4]!=4'h0 && request.arg[2*4+:4]!=4'hf ); + cmd6_invalid[3] <= ( request.arg[3*4+:4]!=4'h0 && request.arg[3*4+:4]!=4'hf ); + cmd6_invalid[4] <= ( request.arg[4*4+:4]!=4'h0 && request.arg[4*4+:4]!=4'hf ); + cmd6_invalid[5] <= ( request.arg[5*4+:4]!=4'h0 && request.arg[5*4+:4]!=4'hf ); + data_response_cmd6stat_init; + cardstatus.illegal_command = 1'b0; + end + 7 : if(request.arg[31:16] == RCA_REG) begin // SELECT_CARD + response_init( 1, 0 , request.cmd , 32 , cardstatus ); + cardstatus.current_state = TRAN; + cardstatus.illegal_command = 1'b0; + end else begin // DESELECT_CARD + cardstatus.current_state = STBY; + cardstatus.illegal_command = 1'b0; + end + 8 : begin // SEND_IF_COND + response_init( 1, 0 , request.cmd , 32 , {24'd1,request.arg[7:0]} ); + cardstatus.illegal_command = 1'b0; + end + 9 : if(request.arg[31:16]==RCA_REG) begin // SEND_CSD + response_init( 1, 0 , 6'b000000 , 120 , CSD_REG ); + cardstatus.illegal_command = 1'b0; + end + 10 : if(request.arg[31:16]==RCA_REG) begin // SEND_CID + response_init( 1, 0 , 6'b000000 , 120 , CID_REG ); + cardstatus.illegal_command = 1'b0; + end + 12 : if(cardstatus.current_state==DATA) begin // STOP_TRANSMISSION + response_init( 1, 0 , request.cmd , 32 , cardstatus ); + data_response_stop; + cardstatus.illegal_command = 1'b0; + end + 13 : if(last_is_acmd) begin // SEND_SD_STATUS + if(cardstatus.current_state==TRAN) begin + cardstatus.app_cmd = 1'b1; + response_init( 1, 0 , request.cmd , 32 , cardstatus ); + data_response_sdstat_init; + cardstatus.illegal_command = 1'b0; + end + end else if(request.arg[31:16]==RCA_REG) begin // SEND_STATUS + response_init( 1, 0 , request.cmd , 32 , cardstatus ); + cardstatus.illegal_command = 1'b0; + end + 15 : if(request.arg[31:16]==RCA_REG) begin // GO_INACTIVE_STATE + response_init( 0, 0 , 0 , 0 , 0 ); + cardstatus.current_state = IDLE; + cardstatus.illegal_command = 1'b0; + end + 16 : if(cardstatus.current_state==TRAN) begin // SET_BLOCKLEN + if(request.arg > 512) cardstatus.block_len_error = 1'b1; + response_init( 1, 0 , request.cmd , 32 , cardstatus ); + cardstatus.illegal_command = 1'b0; + end + 17 : if(cardstatus.current_state==TRAN) begin // READ_SINGLE_BLOCK + response_init( 1, 0 , request.cmd , 32 , cardstatus ); + data_response_init(request.arg); + cardstatus.illegal_command = 1'b0; + end + 18 : if(cardstatus.current_state==TRAN) begin // READ_MULTIPLE_BLOCK + response_init( 1, 0 , request.cmd , 32 , cardstatus ); + data_response_init(request.arg, 1); + cardstatus.illegal_command = 1'b0; + end + 55 : if(request.arg[31:16]=='0 || request.arg[31:16]==RCA_REG) begin // APP_CMD + last_is_acmd <= 1'b1; + cardstatus.app_cmd = 1'b1; + response_init( 1, 0 , request.cmd , 32 , cardstatus ); + cardstatus.illegal_command = 1'b0; + end + 41 : if(last_is_acmd) begin // SD_SEND_OP_COND + cardstatus.app_cmd = 1'b1; + response_init( 1, 1 , 6'b111111 , 32 , OCR_REG ); + cardstatus.illegal_command = 1'b0; + end + 42 : if(last_is_acmd) begin // SET_CLR_CARD_DETECT + cardstatus.app_cmd = 1'b1; + response_init( 1, 0 , request.cmd , 32 , cardstatus ); + cardstatus.illegal_command = 1'b0; + end + 51 : if(last_is_acmd && cardstatus.current_state==TRAN) begin // SEND_SCR + cardstatus.app_cmd = 1'b1; + response_init( 1, 0 , request.cmd , 32 , cardstatus ); + data_response_scr_init; + cardstatus.illegal_command = 1'b0; + end + default : begin // undefined CMD + response_init( 0, 0 , 0 , 0 , 0 ); + cardstatus.illegal_command = 1'b1; + end + endcase + end + response_yield; + data_response_yield; + end + + +always @ (posedge sdclk or negedge rstn_sdclk_p) + if(~rstn_sdclk_p) begin + show_sdcmd_en <= 1'b0; + show_sdcmd_cmd <= '0; + show_sdcmd_arg <= '0; + end else begin + show_sdcmd_en <= 1'b0; + if(respstate == LOADRESP) begin + show_sdcmd_en <= 1'b1; + show_sdcmd_cmd <= request.cmd; + show_sdcmd_arg <= request.arg; + end + end + +endmodule diff --git a/SIM/tb_sd_file_reader.sv b/SIM/tb_sd_file_reader.sv new file mode 100644 index 0000000..400b982 --- /dev/null +++ b/SIM/tb_sd_file_reader.sv @@ -0,0 +1,455 @@ + +//-------------------------------------------------------------------------------------------------------- +// Module : tb_sd_file_reader +// Type : simulation, top +// Standard: SystemVerilog 2005 (IEEE1800-2005) +// Function: testbench for sd_file_reader +// connect sd_file_reader (SD-host) to sd_fake (SD-card) +// sd_file_reader will read sd_fake's content +//-------------------------------------------------------------------------------------------------------- + +`timescale 1ps/1ps + +module tb_sd_file_reader (); + +initial $dumpvars(0, tb_sd_file_reader); + + +//-------------------------------------------------------------------------------------------------------- +// clock and reset +//-------------------------------------------------------------------------------------------------------- +reg rstn = 1'b0; +reg clk = 1'b1; +always #20000 clk = ~clk; // 25MHz +initial begin repeat(4) @ (posedge clk); rstn<=1'b1; end + + +//-------------------------------------------------------------------------------------------------------- +// SDIO bus +//-------------------------------------------------------------------------------------------------------- +wire sdclk; +tri sdcmd; +wire [ 3:0] sddat; + + +//-------------------------------------------------------------------------------------------------------- +// sd_file_reader data out signals +//-------------------------------------------------------------------------------------------------------- +wire outen; +wire [ 7:0] outbyte; +always @ (posedge clk) if(outen) $display("readout byte: %c", outbyte); + + +//-------------------------------------------------------------------------------------------------------- +// sd_file_reader +//-------------------------------------------------------------------------------------------------------- +sd_file_reader #( + .FILE_NAME ( "example.txt" ), + .CLK_DIV ( 0 ), + .SIMULATE ( 1 ) +) sd_file_reader_i ( + .rstn ( rstn ), + .clk ( clk ), + .sdclk ( sdclk ), + .sdcmd ( sdcmd ), + .sddat0 ( sddat[0] ), + .card_stat ( ), + .card_type ( ), + .filesystem_type ( ), + .file_found ( ), + .outen ( outen ), + .outbyte ( outbyte ) +); + + +//-------------------------------------------------------------------------------------------------------- +// sd_fake's memory interface, connect to a ROM which contains SD-card's data +//-------------------------------------------------------------------------------------------------------- +wire rom_req; +wire [39:0] rom_addr; +reg [15:0] rom_data; + + +//-------------------------------------------------------------------------------------------------------- +// monitor parsed request command on sdcmd +//-------------------------------------------------------------------------------------------------------- +wire show_sdcmd_en; +wire [ 5:0] show_sdcmd_cmd; +wire [31:0] show_sdcmd_arg; +always @ (posedge sdclk) if(show_sdcmd_en) $display("sdcmd request: %2d %08x", show_sdcmd_cmd, show_sdcmd_arg); +initial $display("wait for SD-card power up..."); + + +//-------------------------------------------------------------------------------------------------------- +// sd_fake +//-------------------------------------------------------------------------------------------------------- +sd_fake sd_fake_i ( + .rstn_async ( rstn ), + .sdclk ( sdclk ), + .sdcmd ( sdcmd ), + .sddat ( sddat ), + .rdreq ( rom_req ), + .rdaddr ( rom_addr ), + .rddata ( rom_data ), + .show_status_bits ( ), + .show_sdcmd_en ( show_sdcmd_en ), + .show_sdcmd_cmd ( show_sdcmd_cmd ), + .show_sdcmd_arg ( show_sdcmd_arg ) +); + + +//-------------------------------------------------------------------------------------------------------- +// A ROM, contains a complete FAT32 partition data mirror +//-------------------------------------------------------------------------------------------------------- +always @ (posedge sdclk) + if(rom_req) + case(rom_addr) + 40'h00000000df: rom_data <= 16'h8200; + 40'h00000000e0: rom_data <= 16'h0003; + 40'h00000000e1: rom_data <= 16'hd50b; + 40'h00000000e2: rom_data <= 16'hade8; + 40'h00000000e3: rom_data <= 16'h2000; + 40'h00000000e5: rom_data <= 16'hc000; + 40'h00000000e6: rom_data <= 16'h00e6; + 40'h00000000ff: rom_data <= 16'haa55; + 40'h0000200000: rom_data <= 16'h00eb; + 40'h0000200001: rom_data <= 16'h2090; + 40'h0000200002: rom_data <= 16'h2020; + 40'h0000200003: rom_data <= 16'h2020; + 40'h0000200004: rom_data <= 16'h2020; + 40'h0000200005: rom_data <= 16'h0020; + 40'h0000200006: rom_data <= 16'h4002; + 40'h0000200007: rom_data <= 16'h1194; + 40'h0000200008: rom_data <= 16'h0002; + 40'h000020000a: rom_data <= 16'hf800; + 40'h000020000c: rom_data <= 16'h003f; + 40'h000020000d: rom_data <= 16'h00ff; + 40'h000020000e: rom_data <= 16'h2000; + 40'h0000200010: rom_data <= 16'hc000; + 40'h0000200011: rom_data <= 16'h00e6; + 40'h0000200012: rom_data <= 16'h0736; + 40'h0000200016: rom_data <= 16'h0002; + 40'h0000200018: rom_data <= 16'h0001; + 40'h0000200019: rom_data <= 16'h0006; + 40'h0000200020: rom_data <= 16'h0080; + 40'h0000200021: rom_data <= 16'h5929; + 40'h0000200022: rom_data <= 16'he22a; + 40'h0000200023: rom_data <= 16'h4e19; + 40'h0000200024: rom_data <= 16'h204f; + 40'h0000200025: rom_data <= 16'h414e; + 40'h0000200026: rom_data <= 16'h454d; + 40'h0000200027: rom_data <= 16'h2020; + 40'h0000200028: rom_data <= 16'h2020; + 40'h0000200029: rom_data <= 16'h4146; + 40'h000020002a: rom_data <= 16'h3354; + 40'h000020002b: rom_data <= 16'h2032; + 40'h000020002c: rom_data <= 16'h2020; + 40'h00002000ff: rom_data <= 16'haa55; + 40'h0000200100: rom_data <= 16'h5252; + 40'h0000200101: rom_data <= 16'h4161; + 40'h00002001f2: rom_data <= 16'h7272; + 40'h00002001f3: rom_data <= 16'h6141; + 40'h00002001f4: rom_data <= 16'h9a7b; + 40'h00002001f5: rom_data <= 16'h0003; + 40'h00002001f6: rom_data <= 16'h0007; + 40'h00002001ff: rom_data <= 16'haa55; + 40'h00002002ff: rom_data <= 16'haa55; + 40'h0000200600: rom_data <= 16'h00eb; + 40'h0000200601: rom_data <= 16'h2090; + 40'h0000200602: rom_data <= 16'h2020; + 40'h0000200603: rom_data <= 16'h2020; + 40'h0000200604: rom_data <= 16'h2020; + 40'h0000200605: rom_data <= 16'h0020; + 40'h0000200606: rom_data <= 16'h4002; + 40'h0000200607: rom_data <= 16'h1194; + 40'h0000200608: rom_data <= 16'h0002; + 40'h000020060a: rom_data <= 16'hf800; + 40'h000020060c: rom_data <= 16'h003f; + 40'h000020060d: rom_data <= 16'h00ff; + 40'h000020060e: rom_data <= 16'h2000; + 40'h0000200610: rom_data <= 16'hc000; + 40'h0000200611: rom_data <= 16'h00e6; + 40'h0000200612: rom_data <= 16'h0736; + 40'h0000200616: rom_data <= 16'h0002; + 40'h0000200618: rom_data <= 16'h0001; + 40'h0000200619: rom_data <= 16'h0006; + 40'h0000200620: rom_data <= 16'h0080; + 40'h0000200621: rom_data <= 16'h5929; + 40'h0000200622: rom_data <= 16'he22a; + 40'h0000200623: rom_data <= 16'h4e19; + 40'h0000200624: rom_data <= 16'h204f; + 40'h0000200625: rom_data <= 16'h414e; + 40'h0000200626: rom_data <= 16'h454d; + 40'h0000200627: rom_data <= 16'h2020; + 40'h0000200628: rom_data <= 16'h2020; + 40'h0000200629: rom_data <= 16'h4146; + 40'h000020062a: rom_data <= 16'h3354; + 40'h000020062b: rom_data <= 16'h2032; + 40'h000020062c: rom_data <= 16'h2020; + 40'h00002006ff: rom_data <= 16'haa55; + 40'h0000200700: rom_data <= 16'h5252; + 40'h0000200701: rom_data <= 16'h4161; + 40'h00002007f2: rom_data <= 16'h7272; + 40'h00002007f3: rom_data <= 16'h6141; + 40'h00002007f4: rom_data <= 16'hffff; + 40'h00002007f5: rom_data <= 16'hffff; + 40'h00002007f6: rom_data <= 16'hffff; + 40'h00002007f7: rom_data <= 16'hffff; + 40'h00002007ff: rom_data <= 16'haa55; + 40'h00002008ff: rom_data <= 16'haa55; + 40'h0000319400: rom_data <= 16'hfff8; + 40'h0000319401: rom_data <= 16'h0fff; + 40'h0000319402: rom_data <= 16'hffff; + 40'h0000319403: rom_data <= 16'hffff; + 40'h0000319404: rom_data <= 16'hffff; + 40'h0000319405: rom_data <= 16'h0fff; + 40'h0000319406: rom_data <= 16'hffff; + 40'h0000319407: rom_data <= 16'h0fff; + 40'h0000319408: rom_data <= 16'hffff; + 40'h0000319409: rom_data <= 16'h0fff; + 40'h000031940a: rom_data <= 16'hffff; + 40'h000031940b: rom_data <= 16'h0fff; + 40'h000031940c: rom_data <= 16'hffff; + 40'h000031940d: rom_data <= 16'h0fff; + 40'h000038ca00: rom_data <= 16'hfff8; + 40'h000038ca01: rom_data <= 16'h0fff; + 40'h000038ca02: rom_data <= 16'hffff; + 40'h000038ca03: rom_data <= 16'hffff; + 40'h000038ca04: rom_data <= 16'hffff; + 40'h000038ca05: rom_data <= 16'h0fff; + 40'h000038ca06: rom_data <= 16'hffff; + 40'h000038ca07: rom_data <= 16'h0fff; + 40'h000038ca08: rom_data <= 16'hffff; + 40'h000038ca09: rom_data <= 16'h0fff; + 40'h000038ca0a: rom_data <= 16'hffff; + 40'h000038ca0b: rom_data <= 16'h0fff; + 40'h000038ca0c: rom_data <= 16'hffff; + 40'h000038ca0d: rom_data <= 16'h0fff; + 40'h0000400000: rom_data <= 16'h2042; + 40'h0000400001: rom_data <= 16'h4900; + 40'h0000400002: rom_data <= 16'h6e00; + 40'h0000400003: rom_data <= 16'h6600; + 40'h0000400004: rom_data <= 16'h6f00; + 40'h0000400005: rom_data <= 16'h0f00; + 40'h0000400006: rom_data <= 16'h7200; + 40'h0000400007: rom_data <= 16'h0072; + 40'h0000400008: rom_data <= 16'h006d; + 40'h0000400009: rom_data <= 16'h0061; + 40'h000040000a: rom_data <= 16'h0074; + 40'h000040000b: rom_data <= 16'h0069; + 40'h000040000c: rom_data <= 16'h006f; + 40'h000040000e: rom_data <= 16'h006e; + 40'h0000400010: rom_data <= 16'h5301; + 40'h0000400011: rom_data <= 16'h7900; + 40'h0000400012: rom_data <= 16'h7300; + 40'h0000400013: rom_data <= 16'h7400; + 40'h0000400014: rom_data <= 16'h6500; + 40'h0000400015: rom_data <= 16'h0f00; + 40'h0000400016: rom_data <= 16'h7200; + 40'h0000400017: rom_data <= 16'h006d; + 40'h0000400018: rom_data <= 16'h0020; + 40'h0000400019: rom_data <= 16'h0056; + 40'h000040001a: rom_data <= 16'h006f; + 40'h000040001b: rom_data <= 16'h006c; + 40'h000040001c: rom_data <= 16'h0075; + 40'h000040001e: rom_data <= 16'h006d; + 40'h000040001f: rom_data <= 16'h0065; + 40'h0000400020: rom_data <= 16'h5953; + 40'h0000400021: rom_data <= 16'h5453; + 40'h0000400022: rom_data <= 16'h4d45; + 40'h0000400023: rom_data <= 16'h317e; + 40'h0000400024: rom_data <= 16'h2020; + 40'h0000400025: rom_data <= 16'h1620; + 40'h0000400026: rom_data <= 16'h9200; + 40'h0000400027: rom_data <= 16'h91a7; + 40'h0000400028: rom_data <= 16'h4f2a; + 40'h0000400029: rom_data <= 16'h4f2a; + 40'h000040002b: rom_data <= 16'h91a8; + 40'h000040002c: rom_data <= 16'h4f2a; + 40'h000040002d: rom_data <= 16'h0003; + 40'h0000400030: rom_data <= 16'h5845; + 40'h0000400031: rom_data <= 16'h4d41; + 40'h0000400032: rom_data <= 16'h4c50; + 40'h0000400033: rom_data <= 16'h2045; + 40'h0000400034: rom_data <= 16'h5854; + 40'h0000400035: rom_data <= 16'h2054; + 40'h0000400036: rom_data <= 16'h9418; + 40'h0000400037: rom_data <= 16'h91c7; + 40'h0000400038: rom_data <= 16'h4f2a; + 40'h0000400039: rom_data <= 16'h4f2a; + 40'h000040003b: rom_data <= 16'h91ba; + 40'h000040003c: rom_data <= 16'h4f2a; + 40'h000040003d: rom_data <= 16'h0006; + 40'h000040003e: rom_data <= 16'h0019; + 40'h0000404000: rom_data <= 16'h202e; + 40'h0000404001: rom_data <= 16'h2020; + 40'h0000404002: rom_data <= 16'h2020; + 40'h0000404003: rom_data <= 16'h2020; + 40'h0000404004: rom_data <= 16'h2020; + 40'h0000404005: rom_data <= 16'h1020; + 40'h0000404006: rom_data <= 16'h9200; + 40'h0000404007: rom_data <= 16'h91a7; + 40'h0000404008: rom_data <= 16'h4f2a; + 40'h0000404009: rom_data <= 16'h4f2a; + 40'h000040400b: rom_data <= 16'h91a8; + 40'h000040400c: rom_data <= 16'h4f2a; + 40'h000040400d: rom_data <= 16'h0003; + 40'h0000404010: rom_data <= 16'h2e2e; + 40'h0000404011: rom_data <= 16'h2020; + 40'h0000404012: rom_data <= 16'h2020; + 40'h0000404013: rom_data <= 16'h2020; + 40'h0000404014: rom_data <= 16'h2020; + 40'h0000404015: rom_data <= 16'h1020; + 40'h0000404016: rom_data <= 16'h9200; + 40'h0000404017: rom_data <= 16'h91a7; + 40'h0000404018: rom_data <= 16'h4f2a; + 40'h0000404019: rom_data <= 16'h4f2a; + 40'h000040401b: rom_data <= 16'h91a8; + 40'h000040401c: rom_data <= 16'h4f2a; + 40'h0000404020: rom_data <= 16'h7442; + 40'h0000404022: rom_data <= 16'hff00; + 40'h0000404023: rom_data <= 16'hffff; + 40'h0000404024: rom_data <= 16'hffff; + 40'h0000404025: rom_data <= 16'h0fff; + 40'h0000404026: rom_data <= 16'hce00; + 40'h0000404027: rom_data <= 16'hffff; + 40'h0000404028: rom_data <= 16'hffff; + 40'h0000404029: rom_data <= 16'hffff; + 40'h000040402a: rom_data <= 16'hffff; + 40'h000040402b: rom_data <= 16'hffff; + 40'h000040402c: rom_data <= 16'hffff; + 40'h000040402e: rom_data <= 16'hffff; + 40'h000040402f: rom_data <= 16'hffff; + 40'h0000404030: rom_data <= 16'h5701; + 40'h0000404031: rom_data <= 16'h5000; + 40'h0000404032: rom_data <= 16'h5300; + 40'h0000404033: rom_data <= 16'h6500; + 40'h0000404034: rom_data <= 16'h7400; + 40'h0000404035: rom_data <= 16'h0f00; + 40'h0000404036: rom_data <= 16'hce00; + 40'h0000404037: rom_data <= 16'h0074; + 40'h0000404038: rom_data <= 16'h0069; + 40'h0000404039: rom_data <= 16'h006e; + 40'h000040403a: rom_data <= 16'h0067; + 40'h000040403b: rom_data <= 16'h0073; + 40'h000040403c: rom_data <= 16'h002e; + 40'h000040403e: rom_data <= 16'h0064; + 40'h000040403f: rom_data <= 16'h0061; + 40'h0000404040: rom_data <= 16'h5057; + 40'h0000404041: rom_data <= 16'h4553; + 40'h0000404042: rom_data <= 16'h5454; + 40'h0000404043: rom_data <= 16'h317e; + 40'h0000404044: rom_data <= 16'h4144; + 40'h0000404045: rom_data <= 16'h2054; + 40'h0000404046: rom_data <= 16'h9500; + 40'h0000404047: rom_data <= 16'h91a7; + 40'h0000404048: rom_data <= 16'h4f2a; + 40'h0000404049: rom_data <= 16'h4f2a; + 40'h000040404b: rom_data <= 16'h91a8; + 40'h000040404c: rom_data <= 16'h4f2a; + 40'h000040404d: rom_data <= 16'h0004; + 40'h000040404e: rom_data <= 16'h000c; + 40'h0000404050: rom_data <= 16'h4742; + 40'h0000404051: rom_data <= 16'h7500; + 40'h0000404052: rom_data <= 16'h6900; + 40'h0000404053: rom_data <= 16'h6400; + 40'h0000404055: rom_data <= 16'h0f00; + 40'h0000404056: rom_data <= 16'hff00; + 40'h0000404057: rom_data <= 16'hffff; + 40'h0000404058: rom_data <= 16'hffff; + 40'h0000404059: rom_data <= 16'hffff; + 40'h000040405a: rom_data <= 16'hffff; + 40'h000040405b: rom_data <= 16'hffff; + 40'h000040405c: rom_data <= 16'hffff; + 40'h000040405e: rom_data <= 16'hffff; + 40'h000040405f: rom_data <= 16'hffff; + 40'h0000404060: rom_data <= 16'h4901; + 40'h0000404061: rom_data <= 16'h6e00; + 40'h0000404062: rom_data <= 16'h6400; + 40'h0000404063: rom_data <= 16'h6500; + 40'h0000404064: rom_data <= 16'h7800; + 40'h0000404065: rom_data <= 16'h0f00; + 40'h0000404066: rom_data <= 16'hff00; + 40'h0000404067: rom_data <= 16'h0065; + 40'h0000404068: rom_data <= 16'h0072; + 40'h0000404069: rom_data <= 16'h0056; + 40'h000040406a: rom_data <= 16'h006f; + 40'h000040406b: rom_data <= 16'h006c; + 40'h000040406c: rom_data <= 16'h0075; + 40'h000040406e: rom_data <= 16'h006d; + 40'h000040406f: rom_data <= 16'h0065; + 40'h0000404070: rom_data <= 16'h4e49; + 40'h0000404071: rom_data <= 16'h4544; + 40'h0000404072: rom_data <= 16'h4558; + 40'h0000404073: rom_data <= 16'h317e; + 40'h0000404074: rom_data <= 16'h2020; + 40'h0000404075: rom_data <= 16'h2020; + 40'h0000404076: rom_data <= 16'h6600; + 40'h0000404077: rom_data <= 16'h91a8; + 40'h0000404078: rom_data <= 16'h4f2a; + 40'h0000404079: rom_data <= 16'h4f2a; + 40'h000040407b: rom_data <= 16'h91a9; + 40'h000040407c: rom_data <= 16'h4f2a; + 40'h000040407d: rom_data <= 16'h0005; + 40'h000040407e: rom_data <= 16'h004c; + 40'h0000408000: rom_data <= 16'h000c; + 40'h0000408002: rom_data <= 16'h19b9; + 40'h0000408003: rom_data <= 16'h2cb8; + 40'h0000408004: rom_data <= 16'ha4d9; + 40'h0000408005: rom_data <= 16'h8fea; + 40'h000040c000: rom_data <= 16'h007b; + 40'h000040c001: rom_data <= 16'h0038; + 40'h000040c002: rom_data <= 16'h0036; + 40'h000040c003: rom_data <= 16'h0037; + 40'h000040c004: rom_data <= 16'h0044; + 40'h000040c005: rom_data <= 16'h0033; + 40'h000040c006: rom_data <= 16'h0033; + 40'h000040c007: rom_data <= 16'h0031; + 40'h000040c008: rom_data <= 16'h0046; + 40'h000040c009: rom_data <= 16'h002d; + 40'h000040c00a: rom_data <= 16'h0034; + 40'h000040c00b: rom_data <= 16'h0031; + 40'h000040c00c: rom_data <= 16'h0031; + 40'h000040c00d: rom_data <= 16'h0036; + 40'h000040c00e: rom_data <= 16'h002d; + 40'h000040c00f: rom_data <= 16'h0034; + 40'h000040c010: rom_data <= 16'h0038; + 40'h000040c011: rom_data <= 16'h0035; + 40'h000040c012: rom_data <= 16'h0039; + 40'h000040c013: rom_data <= 16'h002d; + 40'h000040c014: rom_data <= 16'h0039; + 40'h000040c015: rom_data <= 16'h0034; + 40'h000040c016: rom_data <= 16'h0046; + 40'h000040c017: rom_data <= 16'h0031; + 40'h000040c018: rom_data <= 16'h002d; + 40'h000040c019: rom_data <= 16'h0045; + 40'h000040c01a: rom_data <= 16'h0036; + 40'h000040c01b: rom_data <= 16'h0032; + 40'h000040c01c: rom_data <= 16'h0037; + 40'h000040c01d: rom_data <= 16'h0034; + 40'h000040c01e: rom_data <= 16'h0046; + 40'h000040c01f: rom_data <= 16'h0032; + 40'h000040c020: rom_data <= 16'h0034; + 40'h000040c021: rom_data <= 16'h0030; + 40'h000040c022: rom_data <= 16'h0035; + 40'h000040c023: rom_data <= 16'h0043; + 40'h000040c024: rom_data <= 16'h0032; + 40'h000040c025: rom_data <= 16'h007d; + 40'h0000410000: rom_data <= 16'h6548; + 40'h0000410001: rom_data <= 16'h6c6c; + 40'h0000410002: rom_data <= 16'h206f; + 40'h0000410003: rom_data <= 16'h6f77; + 40'h0000410004: rom_data <= 16'h6c72; + 40'h0000410005: rom_data <= 16'h2164; + 40'h0000410006: rom_data <= 16'h0a0d; + 40'h0000410007: rom_data <= 16'h7449; + 40'h0000410008: rom_data <= 16'h7720; + 40'h0000410009: rom_data <= 16'h726f; + 40'h000041000a: rom_data <= 16'h736b; + 40'h000041000b: rom_data <= 16'h0d21; + 40'h000041000c: rom_data <= 16'h000a; + default: rom_data <= 16'h0000; + endcase + + +endmodule diff --git a/SIM/tb_sd_file_reader_run_iverilog.bat b/SIM/tb_sd_file_reader_run_iverilog.bat new file mode 100644 index 0000000..24643c9 --- /dev/null +++ b/SIM/tb_sd_file_reader_run_iverilog.bat @@ -0,0 +1,5 @@ +del sim.out dump.vcd +iverilog -g2005-sv -o sim.out tb_sd_file_reader.sv sd_fake.sv ../RTL/sd_file_reader.sv ../RTL/sd_reader.sv ../RTL/sdcmd_ctrl.sv +vvp -n sim.out +del sim.out +pause \ No newline at end of file diff --git a/example-vivado-readfile/Nexys-4-DDR-pins.xdc b/example-vivado-readfile/Nexys-4-DDR-pins.xdc new file mode 100644 index 0000000..757eacf --- /dev/null +++ b/example-vivado-readfile/Nexys-4-DDR-pins.xdc @@ -0,0 +1,41 @@ + +## Clock +set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { clk100mhz }]; #IO_L12P_T1_MRCC_35 Sch=clk100mhz +create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports {clk100mhz}]; + + +set_property -dict { PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports { resetn }]; #IO_L3P_T0_DQS_AD1P_15 Sch=cpu_resetn + +## LEDs +set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { led[0] }]; #IO_L18P_T2_A24_15 Sch=led[0] +set_property -dict { PACKAGE_PIN K15 IOSTANDARD LVCMOS33 } [get_ports { led[1] }]; #IO_L24P_T3_RS1_15 Sch=led[1] +set_property -dict { PACKAGE_PIN J13 IOSTANDARD LVCMOS33 } [get_ports { led[2] }]; #IO_L17N_T2_A25_15 Sch=led[2] +set_property -dict { PACKAGE_PIN N14 IOSTANDARD LVCMOS33 } [get_ports { led[3] }]; #IO_L8P_T1_D11_14 Sch=led[3] +set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { led[4] }]; #IO_L7P_T1_D09_14 Sch=led[4] +set_property -dict { PACKAGE_PIN V17 IOSTANDARD LVCMOS33 } [get_ports { led[5] }]; #IO_L18N_T2_A11_D27_14 Sch=led[5] +set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { led[6] }]; #IO_L17P_T2_A14_D30_14 Sch=led[6] +set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { led[7] }]; #IO_L18P_T2_A12_D28_14 Sch=led[7] +set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { led[8] }]; #IO_L16N_T2_A15_D31_14 Sch=led[8] +set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { led[9] }]; #IO_L14N_T2_SRCC_14 Sch=led[9] +set_property -dict { PACKAGE_PIN U14 IOSTANDARD LVCMOS33 } [get_ports { led[10] }]; #IO_L22P_T3_A05_D21_14 Sch=led[10] +set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS33 } [get_ports { led[11] }]; #IO_L15N_T2_DQS_DOUT_CSO_B_14 Sch=led[11] +set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { led[12] }]; #IO_L16P_T2_CSI_B_14 Sch=led[12] +set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { led[13] }]; #IO_L22N_T3_A04_D20_14 Sch=led[13] +set_property -dict { PACKAGE_PIN V12 IOSTANDARD LVCMOS33 } [get_ports { led[14] }]; #IO_L20N_T3_A07_D23_14 Sch=led[14] +set_property -dict { PACKAGE_PIN V11 IOSTANDARD LVCMOS33 } [get_ports { led[15] }]; #IO_L21N_T3_DQS_A06_D22_14 Sch=led[15] + + +# UART +set_property -dict { PACKAGE_PIN D4 IOSTANDARD LVCMOS33 } [get_ports { uart_tx }]; #IO_L11N_T1_SRCC_35 Sch=uart_rxd_out + + +# SDcard +set_property -dict { PACKAGE_PIN E2 IOSTANDARD LVCMOS33 } [get_ports { sdcard_pwr_n }]; #IO_L14P_T2_SRCC_35 Sch=sd_resetn +#set_property -dict { PACKAGE_PIN A1 IOSTANDARD LVCMOS33 } [get_ports { sd_cd }]; #IO_L9N_T1_DQS_AD7N_35 Sch=sd_cd +set_property -dict { PACKAGE_PIN B1 IOSTANDARD LVCMOS33 } [get_ports { sdclk }]; #IO_L9P_T1_DQS_AD7P_35 Sch=sdclk +set_property -dict { PACKAGE_PIN C1 IOSTANDARD LVCMOS33 } [get_ports { sdcmd }]; #IO_L16N_T2_35 Sch=sdcmd +set_property -dict { PACKAGE_PIN C2 IOSTANDARD LVCMOS33 } [get_ports { sddat0 }]; #IO_L16P_T2_35 Sch=sd_dat[0] +set_property -dict { PACKAGE_PIN E1 IOSTANDARD LVCMOS33 } [get_ports { sddat1 }]; #IO_L18N_T2_35 Sch=sd_dat[1] +set_property -dict { PACKAGE_PIN F1 IOSTANDARD LVCMOS33 } [get_ports { sddat2 }]; #IO_L18P_T2_35 Sch=sd_dat[2] +set_property -dict { PACKAGE_PIN D2 IOSTANDARD LVCMOS33 } [get_ports { sddat3 }]; #IO_L14N_T2_SRCC_35 Sch=sd_dat[3] + diff --git a/example-vivado-readfile/Nexys4-ReadFile.xpr b/example-vivado-readfile/Nexys4-ReadFile.xpr new file mode 100644 index 0000000..dbccc20 --- /dev/null +++ b/example-vivado-readfile/Nexys4-ReadFile.xpr @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + default_dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example-vivado-readfile/RTL/top.sv b/example-vivado-readfile/RTL/top.sv new file mode 100644 index 0000000..4ce66ac --- /dev/null +++ b/example-vivado-readfile/RTL/top.sv @@ -0,0 +1,84 @@ + +//-------------------------------------------------------------------------------------------------------- +// Module : top +// Type : synthesizable, FPGA's top, IP's example design +// Standard: SystemVerilog 2005 (IEEE1800-2005) +// Function: an example of sd_file_reader, read a file from SDcard and send file content to UART +// this example runs on Digilent Nexys4-DDR board (Xilinx Artix-7), +// see http://www.digilent.com.cn/products/product-nexys-4-ddr-artix-7-fpga-trainer-board.html +//-------------------------------------------------------------------------------------------------------- + +module top ( + // clock = 100MHz + input wire clk100mhz, + // rstn active-low, You can re-read SDcard by pushing the reset button. + input wire resetn, + // when sdcard_pwr_n = 0, SDcard power on + output wire sdcard_pwr_n, + // signals connect to SD bus + output wire sdclk, + inout sdcmd, + input wire sddat0, + output wire sddat1, sddat2, sddat3, + // 16 bit led to show the status of SDcard + output wire [15:0] led, + // UART tx signal, connected to host-PC's UART-RXD, baud=115200 + output wire uart_tx +); + +assign led[15:9] = '0; + +assign sdcard_pwr_n = 1'b0; + +assign {sddat1, sddat2, sddat3} = 3'b111; // Must set sddat1~3 to 1 to avoid SD card from entering SPI mode + + +wire outen; // when outen=1, a byte of file content is read out from outbyte +wire [7:0] outbyte; // a byte of file content + + +//---------------------------------------------------------------------------------------------------- +// sd_file_reader +//---------------------------------------------------------------------------------------------------- +sd_file_reader #( + .FILE_NAME ( "example.txt" ), // file name to read + .CLK_DIV ( 2 ), // because clk=100MHz, CLK_DIV is set to 2 + .SIMULATE ( 0 ) +) sd_file_reader_i ( + .rstn ( resetn ), + .clk ( clk100mhz ), + .sdclk ( sdclk ), + .sdcmd ( sdcmd ), + .sddat0 ( sddat0 ), + .card_stat ( led[3:0] ), // show the sdcard initialize status + .card_type ( led[5:4] ), // 0=UNKNOWN , 1=SDv1 , 2=SDv2 , 3=SDHCv2 + .filesystem_type ( led[7:6] ), // 0=UNASSIGNED , 1=UNKNOWN , 2=FAT16 , 3=FAT32 + .file_found ( led[ 8] ), // 0=file not found, 1=file found + .outen ( outen ), + .outbyte ( outbyte ) +); + + +//---------------------------------------------------------------------------------------------------- +// send file content to UART +//---------------------------------------------------------------------------------------------------- +uart_tx #( + .CLK_DIV ( 868 ), // 100MHz/868 = 115200 + .PARITY ( "NONE" ), // no parity bit + .ASIZE ( 14 ), // + .DWIDTH ( 1 ), // tx_data is 8 bit (1 Byte) + .ENDIAN ( "LITTLE" ), // + .MODE ( "RAW" ), // + .END_OF_DATA ( "" ), // + .END_OF_PACK ( "" ) // +) uart_tx_i ( + .rstn ( resetn ), + .clk ( clk100mhz ), + .tx_data ( outbyte ), + .tx_last ( 1'b0 ), + .tx_en ( outen ), + .tx_rdy ( ), + .o_uart_tx ( uart_tx ) +); + +endmodule diff --git a/example-vivado-readfile/RTL/uart_tx.sv b/example-vivado-readfile/RTL/uart_tx.sv new file mode 100644 index 0000000..62e62c6 --- /dev/null +++ b/example-vivado-readfile/RTL/uart_tx.sv @@ -0,0 +1,188 @@ + +//-------------------------------------------------------------------------------------------------------- +// Module : uart_tx +// Type : synthesizable, IP's top +// Standard: SystemVerilog 2005 (IEEE1800-2005) +// Function: buffer input data and send them to UART +// UART format: 8 data bits +//-------------------------------------------------------------------------------------------------------- + +module uart_tx #( + parameter CLK_DIV = 434, // UART baud rate = clk freq/(2*UART_TX_CLK_DIV). for example, when clk=50MHz, UART_TX_CLK_DIV=434, then baud=50MHz/(2*434)=115200 + parameter PARITY = "NONE", // "NONE", "ODD" or "EVEN" + parameter ASIZE = 10, // UART TX buffer size = 2^ASIZE bytes, Set it smaller if your FPGA doesn't have enough BRAM + parameter DWIDTH = 1, // Specify width of tx_data , that is, how many bytes can it input per clock cycle + parameter ENDIAN = "LITTLE", // "LITTLE" or "BIG". when DWIDTH>=2, this parameter determines the byte order of tx_data + parameter MODE = "RAW", // "RAW", "PRINTABLE", "HEX" or "HEXSPACE" + parameter END_OF_DATA = "", // Specify a extra send byte after each tx_data. when ="", do not send this extra byte + parameter END_OF_PACK = "" // Specify a extra send byte after each tx_data with tx_last=1. when ="", do not send this extra byte +)( + input wire rstn, + input wire clk, + // user interface + input wire [DWIDTH*8-1:0] tx_data, + input wire tx_last, + input wire tx_en, + output wire tx_rdy, + // uart tx output signal + output reg o_uart_tx +); + +initial o_uart_tx = 1'b1; + + + +function automatic logic [7:0] hex2ascii (input [3:0] hex); + return {4'h3, hex} + ((hex<4'hA) ? 8'h0 : 8'h7) ; +endfunction + + +function automatic logic is_printable_ascii(input [7:0] ascii); + return (ascii>=8'h20 && ascii<8'h7F) || ascii==8'h0A || ascii==8'h0D; +endfunction + + +function automatic logic [11:0] build_send_byte(input [7:0] send_byte); + if ( PARITY == "ODD" ) + return {1'b1, (~(^send_byte)), send_byte, 2'b01}; + else if( PARITY == "EVEN" ) + return {1'b1, (^send_byte) , send_byte, 2'b01}; + else + return {1'b1, 1'b1 , send_byte, 2'b01}; +endfunction + + +function automatic logic [6+35:0] build_send_data(input [7:0] send_data); + logic [ 5:0] dcnt = '0; + logic [35:0] data = '1; + if( MODE != "PRINTABLE" || is_printable_ascii(send_data) ) begin + if( MODE == "HEXSPACE" ) begin + dcnt += 6'd12; + data[11:0] = build_send_byte(8'h20); + end + dcnt += 6'd12; + data <<= 12; + if( MODE == "HEX" || MODE == "HEXSPACE" ) begin + data[11:0] = build_send_byte(hex2ascii(send_data[3:0])); + dcnt += 6'd12; + data <<= 12; + data[11:0] = build_send_byte(hex2ascii(send_data[7:4])); + end else begin + data[11:0] = build_send_byte(send_data); + end + end + return {dcnt, data}; +endfunction + + +function automatic logic [6+35:0] build_send_eod(input send_last); + logic [ 5:0] dcnt = '0; + logic [35:0] data = '1; + if( END_OF_PACK != "" && send_last ) begin + dcnt += 6'd12; + data[11:0] = build_send_byte((8)'(END_OF_PACK)); + end + if( END_OF_DATA != "" ) begin + dcnt += 6'd12; + data <<= 12; + data[11:0] = build_send_byte((8)'(END_OF_DATA)); + end + return {dcnt, data}; +endfunction + + + + +reg [DWIDTH*8-1:0] tx_data_endian; + +always_comb + if(ENDIAN == "BIG") begin + for(int i=0; i '0 ) begin + if( cyc+1 == CLK_DIV ) begin + txdcnt <= txdcnt - 6'd1; + {txdata, o_uart_tx} <= {1'b1, txdata}; + end + end else if( bcnt > '0 ) begin + bcnt <= bcnt - 16'd1; + send_data <= send_data >> 8; + {txdcnt, txdata} <= build_send_data(send_data[7:0]); + end else if( eod ) begin + eod <= '0; + {txdcnt, txdata} <= build_send_eod(send_last); + end else if( fifo_empty_n ) begin + fifo_rptr <= fifo_rptr + (ASIZE+1)'(1); + fifo_rd_en <= 1'b1; + end + end + + +endmodule diff --git a/example-vivado-readsector/Nexys-4-DDR-pins.xdc b/example-vivado-readsector/Nexys-4-DDR-pins.xdc new file mode 100644 index 0000000..757eacf --- /dev/null +++ b/example-vivado-readsector/Nexys-4-DDR-pins.xdc @@ -0,0 +1,41 @@ + +## Clock +set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { clk100mhz }]; #IO_L12P_T1_MRCC_35 Sch=clk100mhz +create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports {clk100mhz}]; + + +set_property -dict { PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports { resetn }]; #IO_L3P_T0_DQS_AD1P_15 Sch=cpu_resetn + +## LEDs +set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { led[0] }]; #IO_L18P_T2_A24_15 Sch=led[0] +set_property -dict { PACKAGE_PIN K15 IOSTANDARD LVCMOS33 } [get_ports { led[1] }]; #IO_L24P_T3_RS1_15 Sch=led[1] +set_property -dict { PACKAGE_PIN J13 IOSTANDARD LVCMOS33 } [get_ports { led[2] }]; #IO_L17N_T2_A25_15 Sch=led[2] +set_property -dict { PACKAGE_PIN N14 IOSTANDARD LVCMOS33 } [get_ports { led[3] }]; #IO_L8P_T1_D11_14 Sch=led[3] +set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { led[4] }]; #IO_L7P_T1_D09_14 Sch=led[4] +set_property -dict { PACKAGE_PIN V17 IOSTANDARD LVCMOS33 } [get_ports { led[5] }]; #IO_L18N_T2_A11_D27_14 Sch=led[5] +set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { led[6] }]; #IO_L17P_T2_A14_D30_14 Sch=led[6] +set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { led[7] }]; #IO_L18P_T2_A12_D28_14 Sch=led[7] +set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { led[8] }]; #IO_L16N_T2_A15_D31_14 Sch=led[8] +set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { led[9] }]; #IO_L14N_T2_SRCC_14 Sch=led[9] +set_property -dict { PACKAGE_PIN U14 IOSTANDARD LVCMOS33 } [get_ports { led[10] }]; #IO_L22P_T3_A05_D21_14 Sch=led[10] +set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS33 } [get_ports { led[11] }]; #IO_L15N_T2_DQS_DOUT_CSO_B_14 Sch=led[11] +set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { led[12] }]; #IO_L16P_T2_CSI_B_14 Sch=led[12] +set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { led[13] }]; #IO_L22N_T3_A04_D20_14 Sch=led[13] +set_property -dict { PACKAGE_PIN V12 IOSTANDARD LVCMOS33 } [get_ports { led[14] }]; #IO_L20N_T3_A07_D23_14 Sch=led[14] +set_property -dict { PACKAGE_PIN V11 IOSTANDARD LVCMOS33 } [get_ports { led[15] }]; #IO_L21N_T3_DQS_A06_D22_14 Sch=led[15] + + +# UART +set_property -dict { PACKAGE_PIN D4 IOSTANDARD LVCMOS33 } [get_ports { uart_tx }]; #IO_L11N_T1_SRCC_35 Sch=uart_rxd_out + + +# SDcard +set_property -dict { PACKAGE_PIN E2 IOSTANDARD LVCMOS33 } [get_ports { sdcard_pwr_n }]; #IO_L14P_T2_SRCC_35 Sch=sd_resetn +#set_property -dict { PACKAGE_PIN A1 IOSTANDARD LVCMOS33 } [get_ports { sd_cd }]; #IO_L9N_T1_DQS_AD7N_35 Sch=sd_cd +set_property -dict { PACKAGE_PIN B1 IOSTANDARD LVCMOS33 } [get_ports { sdclk }]; #IO_L9P_T1_DQS_AD7P_35 Sch=sdclk +set_property -dict { PACKAGE_PIN C1 IOSTANDARD LVCMOS33 } [get_ports { sdcmd }]; #IO_L16N_T2_35 Sch=sdcmd +set_property -dict { PACKAGE_PIN C2 IOSTANDARD LVCMOS33 } [get_ports { sddat0 }]; #IO_L16P_T2_35 Sch=sd_dat[0] +set_property -dict { PACKAGE_PIN E1 IOSTANDARD LVCMOS33 } [get_ports { sddat1 }]; #IO_L18N_T2_35 Sch=sd_dat[1] +set_property -dict { PACKAGE_PIN F1 IOSTANDARD LVCMOS33 } [get_ports { sddat2 }]; #IO_L18P_T2_35 Sch=sd_dat[2] +set_property -dict { PACKAGE_PIN D2 IOSTANDARD LVCMOS33 } [get_ports { sddat3 }]; #IO_L14N_T2_SRCC_35 Sch=sd_dat[3] + diff --git a/example-vivado-readsector/Nexys4-ReadSector.xpr b/example-vivado-readsector/Nexys4-ReadSector.xpr new file mode 100644 index 0000000..968a7d2 --- /dev/null +++ b/example-vivado-readsector/Nexys4-ReadSector.xpr @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + default_dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example-vivado-readsector/RTL/top.sv b/example-vivado-readsector/RTL/top.sv new file mode 100644 index 0000000..dc365e8 --- /dev/null +++ b/example-vivado-readsector/RTL/top.sv @@ -0,0 +1,96 @@ + +//-------------------------------------------------------------------------------------------------------- +// Module : top +// Type : synthesizable, FPGA's top, IP's example design +// Standard: SystemVerilog 2005 (IEEE1800-2005) +// Function: an example of sd_reader, read a sector (512B) from SDcard and send its content to UART +// this example runs on Digilent Nexys4-DDR board (Xilinx Artix-7), +// see http://www.digilent.com.cn/products/product-nexys-4-ddr-artix-7-fpga-trainer-board.html +//-------------------------------------------------------------------------------------------------------- + +module top ( + // clock = 100MHz + input wire clk100mhz, + // rstn active-low, You can re-read SDcard by pushing the reset button. + input wire resetn, + // when sdcard_pwr_n = 0, SDcard power on + output wire sdcard_pwr_n, + // signals connect to SD bus + output wire sdclk, + inout sdcmd, + input wire sddat0, + output wire sddat1, sddat2, sddat3, + // 16 bit led to show the status of SDcard + output wire [15:0] led, + // UART tx signal, connected to host-PC's UART-RXD, baud=115200 + output wire uart_tx +); + +assign led[15:6] = 10'h0; + +assign sdcard_pwr_n = 1'b0; + +assign {sddat1, sddat2, sddat3} = 3'b111; // Must set sddat1~3 to 1 to avoid SD card from entering SPI mode + + +wire rdone; +reg rstart = 1'b1; + +wire outen; +wire [7:0] outbyte; + +always @ (posedge clk100mhz or negedge resetn) + if(~resetn) begin + rstart <= 1'b1; + end else begin + if(rdone) rstart <= 1'b0; // read a sector only once, so set rstart=0 when rdone=1. + end + + +//---------------------------------------------------------------------------------------------------- +// sd_reader +//---------------------------------------------------------------------------------------------------- +sd_reader #( + .CLK_DIV ( 2 ), + .SIMULATE ( 0 ) +) sd_reader_i ( + .rstn ( resetn ), + .clk ( clk100mhz ), + .sdclk ( sdclk ), + .sdcmd ( sdcmd ), + .sddat0 ( sddat0 ), + .card_stat ( led[ 3: 0] ), // show the sdcard initialize status + .card_type ( led[ 5: 4] ), // 0=UNKNOWN , 1=SDv1 , 2=SDv2 , 3=SDHCv2 + .rstart ( rstart ), + .rsector ( 0 ), // read No. 0 sector (the first sector) in SDcard + .rbusy ( ), + .rdone ( rdone ), + .outen ( outen ), + .outaddr ( ), + .outbyte ( outbyte ) +); + + +//---------------------------------------------------------------------------------------------------- +// send file content to UART +//---------------------------------------------------------------------------------------------------- +uart_tx #( + .CLK_DIV ( 868 ), // 100MHz/868 = 115200 + .PARITY ( "NONE" ), // no parity bit + .ASIZE ( 14 ), // + .DWIDTH ( 1 ), // tx_data is 8 bit (1 Byte) + .ENDIAN ( "LITTLE" ), // + .MODE ( "RAW" ), // + .END_OF_DATA ( "" ), // + .END_OF_PACK ( "" ) // +) uart_tx_i ( + .rstn ( resetn ), + .clk ( clk100mhz ), + .tx_data ( outbyte ), + .tx_last ( 1'b0 ), + .tx_en ( outen ), + .tx_rdy ( ), + .o_uart_tx ( uart_tx ) +); + +endmodule diff --git a/example-vivado-readsector/RTL/uart_tx.sv b/example-vivado-readsector/RTL/uart_tx.sv new file mode 100644 index 0000000..62e62c6 --- /dev/null +++ b/example-vivado-readsector/RTL/uart_tx.sv @@ -0,0 +1,188 @@ + +//-------------------------------------------------------------------------------------------------------- +// Module : uart_tx +// Type : synthesizable, IP's top +// Standard: SystemVerilog 2005 (IEEE1800-2005) +// Function: buffer input data and send them to UART +// UART format: 8 data bits +//-------------------------------------------------------------------------------------------------------- + +module uart_tx #( + parameter CLK_DIV = 434, // UART baud rate = clk freq/(2*UART_TX_CLK_DIV). for example, when clk=50MHz, UART_TX_CLK_DIV=434, then baud=50MHz/(2*434)=115200 + parameter PARITY = "NONE", // "NONE", "ODD" or "EVEN" + parameter ASIZE = 10, // UART TX buffer size = 2^ASIZE bytes, Set it smaller if your FPGA doesn't have enough BRAM + parameter DWIDTH = 1, // Specify width of tx_data , that is, how many bytes can it input per clock cycle + parameter ENDIAN = "LITTLE", // "LITTLE" or "BIG". when DWIDTH>=2, this parameter determines the byte order of tx_data + parameter MODE = "RAW", // "RAW", "PRINTABLE", "HEX" or "HEXSPACE" + parameter END_OF_DATA = "", // Specify a extra send byte after each tx_data. when ="", do not send this extra byte + parameter END_OF_PACK = "" // Specify a extra send byte after each tx_data with tx_last=1. when ="", do not send this extra byte +)( + input wire rstn, + input wire clk, + // user interface + input wire [DWIDTH*8-1:0] tx_data, + input wire tx_last, + input wire tx_en, + output wire tx_rdy, + // uart tx output signal + output reg o_uart_tx +); + +initial o_uart_tx = 1'b1; + + + +function automatic logic [7:0] hex2ascii (input [3:0] hex); + return {4'h3, hex} + ((hex<4'hA) ? 8'h0 : 8'h7) ; +endfunction + + +function automatic logic is_printable_ascii(input [7:0] ascii); + return (ascii>=8'h20 && ascii<8'h7F) || ascii==8'h0A || ascii==8'h0D; +endfunction + + +function automatic logic [11:0] build_send_byte(input [7:0] send_byte); + if ( PARITY == "ODD" ) + return {1'b1, (~(^send_byte)), send_byte, 2'b01}; + else if( PARITY == "EVEN" ) + return {1'b1, (^send_byte) , send_byte, 2'b01}; + else + return {1'b1, 1'b1 , send_byte, 2'b01}; +endfunction + + +function automatic logic [6+35:0] build_send_data(input [7:0] send_data); + logic [ 5:0] dcnt = '0; + logic [35:0] data = '1; + if( MODE != "PRINTABLE" || is_printable_ascii(send_data) ) begin + if( MODE == "HEXSPACE" ) begin + dcnt += 6'd12; + data[11:0] = build_send_byte(8'h20); + end + dcnt += 6'd12; + data <<= 12; + if( MODE == "HEX" || MODE == "HEXSPACE" ) begin + data[11:0] = build_send_byte(hex2ascii(send_data[3:0])); + dcnt += 6'd12; + data <<= 12; + data[11:0] = build_send_byte(hex2ascii(send_data[7:4])); + end else begin + data[11:0] = build_send_byte(send_data); + end + end + return {dcnt, data}; +endfunction + + +function automatic logic [6+35:0] build_send_eod(input send_last); + logic [ 5:0] dcnt = '0; + logic [35:0] data = '1; + if( END_OF_PACK != "" && send_last ) begin + dcnt += 6'd12; + data[11:0] = build_send_byte((8)'(END_OF_PACK)); + end + if( END_OF_DATA != "" ) begin + dcnt += 6'd12; + data <<= 12; + data[11:0] = build_send_byte((8)'(END_OF_DATA)); + end + return {dcnt, data}; +endfunction + + + + +reg [DWIDTH*8-1:0] tx_data_endian; + +always_comb + if(ENDIAN == "BIG") begin + for(int i=0; i '0 ) begin + if( cyc+1 == CLK_DIV ) begin + txdcnt <= txdcnt - 6'd1; + {txdata, o_uart_tx} <= {1'b1, txdata}; + end + end else if( bcnt > '0 ) begin + bcnt <= bcnt - 16'd1; + send_data <= send_data >> 8; + {txdcnt, txdata} <= build_send_data(send_data[7:0]); + end else if( eod ) begin + eod <= '0; + {txdcnt, txdata} <= build_send_eod(send_last); + end else if( fifo_empty_n ) begin + fifo_rptr <= fifo_rptr + (ASIZE+1)'(1); + fifo_rd_en <= 1'b1; + end + end + + +endmodule diff --git a/figures/pin.png b/figures/pin.png new file mode 100644 index 0000000000000000000000000000000000000000..f840aa3d3ecbe375ca64c97e2f310daca40a6f04 GIT binary patch literal 23691 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