diff --git a/doc/guide/sdcard-setup.md b/doc/guide/sdcard-setup.md new file mode 100644 index 000000000..c72dc434e --- /dev/null +++ b/doc/guide/sdcard-setup.md @@ -0,0 +1,38 @@ +# Setting up SD card image for 'sdcard_tests.hh' + +To set up a FAT32-formatted SD card image 'sd.img' on a Linux system, the following +may be useful: + +https://github.com/procount/fat32images offers various ready-to-download volume images. +The 1GiB example (nnobs1gb.img.zip) should suffice; rename it to 'sd.img' + +Find the offset of the FAT32 partition: + +> fdisk -u sd.img +``` +Disk sd.img: 1 GiB, 1073741824 bytes, 2097152 sectors +Units: sectors of 1 * 512 = 512 bytes +Sector size (logical/physical): 512 bytes / 512 bytes +I/O size (minimum/optimal): 512 bytes / 512 bytes +Disklabel type: dos +Disk identifier: 0x6c4676d7 + +Device Boot Start End Sectors Size Id Type +noobs1gb.img1 8192 2097151 2088960 1020M c W95 FAT32 (LBA) +``` + +Note the 'Start' value (8192) and multiply it by the sector size (512 bytes), +e.g. 4194304 in the command below: + +Create a mount point within the filing system for the FAT32 partition. +> mkdir mnt_point + +Make the FAT32 partition available. +> sudo mount -o loop,offset=4194304 mnt_point sd.img + +Copy the lorem ipsum text (or other files) into the root directory of the FAT32 partition. +> sudo cp mnt_point/lorem.ips + +Unmount the FAT32 partition. +> sudo umount mnt_point + diff --git a/dv/dpi/spidpi/spi_microsd.cc b/dv/dpi/spidpi/spi_microsd.cc new file mode 100644 index 000000000..a8fa905fd --- /dev/null +++ b/dv/dpi/spidpi/spi_microsd.cc @@ -0,0 +1,334 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/** + * This DPI model provides a simple implementation of a SPI-connected microSD card. + * It provides enough functionality for the existing tests to run in simulation + * but little more than that. + * + * Single Block Read/Write commands are supported and the normal initialisation + * sequence. + * + * The code expects a file of the name specified to the constructor, e.g. 'sd.img', + * and will operate on that as if it were an SD card, performing block transfers + * to/from that file. + */ + +#include + +#include "spi_microsd.hh" + +// SD command codes. +enum { + CMD_GO_IDLE_STATE = 0, + CMD_SEND_OP_COND = 1, + CMD_SEND_IF_COND = 8, + CMD_SEND_CSD = 9, + CMD_SEND_CID = 10, + CMD_STOP_TRANSMISSION = 12, + CMD_SET_BLOCKLEN = 16, + CMD_READ_SINGLE_BLOCK = 17, + CMD_READ_MULTIPLE_BLOCK = 18, + CMD_WRITE_SINGLE_BLOCK = 24, + CMD_WRITE_MULTIPLE_BLOCK = 25, + SD_SEND_OP_COND = 41, + CMD_APP_CMD = 55, + CMD_READ_OCR = 58, + CMD_CRC_ON_OFF = 59, +}; + +// The CI system presently does not have the means to obtain/create a suitable +// SD card image ('sd.img') for use in simulation so set this to 'false' to indicate +// the presence of a card and leave the test code to spot from the CID/CSD that it +// is running in simulation. +static const bool kMustHaveSD = false; + +void spi_microsd::reset() { + spidpi::reset(); + cmdBytes = 0u; + responding = false; + reading = false; + writing = false; + + // Initialise the CID + memset(cid, 0, sizeof(cid)); + // Provide some suggestion of valid details if we have an 'sd.img'; the CI system + // checks the first 3 bytes in simulation to ascertain whether it should attempt + // block transfers and filing system testing. + if (sd) { + cid[0] = 0x70; + cid[1] = 'l'; + cid[2] = 'R'; + } + cid[15] = update_crc7(0, cid, 15); + // Initialise the CSD + memset(csd, 0, sizeof(csd)); + csd[15] = update_crc7(0, csd, 15); +} + +void spi_microsd::writeByte(uint8_t inByte, uint32_t oobIn) { + logText("microSD %0x\n", inByte); + + if (writing) { + switch (writeState) { + case WriteState_StartToken: + if (inByte == 0xfeu) { + writeState = WriteState_DataBytes; + // CRC16 is seeded with all zero bits. + writeCrc16 = 0u; + } + break; + case WriteState_DataBytes: + if (writeBytes < sizeof(writeBuf)) { + writeBuf[writeBytes] = inByte; + } + writeCrc16 = update_crc16(writeCrc16, &inByte, 1u); + if (++writeBytes >= kBlockLen) { + writeState = WriteState_CRC0; + } + break; + case WriteState_CRC0: + if (inByte != (uint8_t)(writeCrc16 >> 8)) { + logText("Mismatched CRC16 on write data"); + } + writeState = WriteState_CRC1; + break; + case WriteState_CRC1: + if (inByte != (uint8_t)writeCrc16) { + logText("Mismatched CRC16 on write data"); + } + writeState = WriteState_StartToken; + // The data_response is 0xe5 indicating that the write data has been accepted, + // and then '0' bytes whilst busy until Idle again. + sendResponse(8u, ((uint64_t)0x01 << 56) | 0xe5); + writing = false; + // At this point we write out the data block. + if (sd && kBlockLen != fwrite(writeBuf, 1, kBlockLen, sd)) { + logText("Failed writing to SD card"); + } + break; + default: + break; + } + } else { + switch (cmdBytes) { + case 0u: + if ((inByte & 0xc0) == 0x40) { + cmd.cmdCode = inByte & 0x3f; + cmdBytes = 1u; + } + break; + case 5u: + cmd.crc = inByte; + startCommand(); + cmdBytes = 0u; + break; + default: + // Collect the next byte of the address. + cmd.address = (cmd.address << 8) | inByte; + cmdBytes++; + break; + } + } +} + +bool spi_microsd::readByte(uint32_t oobIn, uint8_t &outByte, uint32_t &oobOut) { + if (responding) { + if (rspBytes < rspLen) { + outByte = rsp[rspBytes++]; + // OOB data carries SD card detect - absent (1) or present (0). + oobOut = kMustHaveSD && !sd; + return true; + } + if (reading) { + readState = ReadState_DataBytes; + // CRC16 is seeded with all zero bits. + readCrc16 = 0u; + } + responding = false; + } + if (reading) { + // Checksum? + switch (readState) { + case ReadState_DataBytes: { + switch (cmd.cmdCode) { + case CMD_SEND_CID: + outByte = cid[readBytes]; + readCrc16 = update_crc16(readCrc16, &outByte, 1u); + if (++readBytes >= sizeof(cid)) { + readState = ReadState_CRC0; + } + break; + case CMD_SEND_CSD: + outByte = csd[readBytes]; + readCrc16 = update_crc16(readCrc16, &outByte, 1u); + if (++readBytes >= sizeof(csd)) { + readState = ReadState_CRC0; + } + break; + case CMD_READ_MULTIPLE_BLOCK: + case CMD_READ_SINGLE_BLOCK: { + // Return the next byte from the memory. + int ch = 0xffu; + if (sd) { + ch = fgetc(sd); + if (ch == EOF) { + logText("Failed reading from SD card"); + } + } + outByte = ch; + readCrc16 = update_crc16(readCrc16, &outByte, 1u); + if (!(++readBytes & (kBlockLen - 1u))) { + readState = ReadState_CRC0; + } + } + break; + default: + break; + } + } + break; + case ReadState_CRC0: + outByte = (uint8_t)(readCrc16 >> 8); + readState = ReadState_CRC1; + break; + case ReadState_CRC1: + readBytes = 0u; + outByte = (uint8_t)readCrc16; + readState = ReadState_DataBytes; + break; + default: + outByte = 0xffu; + reading = false; + break; + } + + // OOB data carries SD card detect - absent (1) or present (0). + oobOut = kMustHaveSD && !sd; + logText("Read byte 0x%0x\n", outByte); + return true; + } else { + return false; + } +} + +// Transition on one or more CS lines. +void spi_microsd::csChanged(bool csAsserted, uint32_t oobIn) { + cmdBytes = 0u; + responding = false; + reading = false; +} + +// TODO: prevent command processing trying to read from non-extant SD card; +// reject out-of-order command codes etc. +void spi_microsd::startCommand() { + logText("Starting command %02x,%08x,%02x", cmd.cmdCode, cmd.address, cmd.crc); + switch (cmd.cmdCode) { + case CMD_GO_IDLE_STATE: + sendResponse(1u, 0x01); + break; + case CMD_SEND_IF_COND: + sendResponse(5u, 0); + break; + + // Card Identification Data and Card Specific Data may be handled similarly. + case CMD_SEND_CID: + case CMD_SEND_CSD: + sendResponse(2u, 0xfe00); + reading = true; + readState = ReadState_CmdStatus; + readBytes = 0u; + break; + + case CMD_STOP_TRANSMISSION: + break; + case CMD_SET_BLOCKLEN: + sendResponse(1u, 0); + break; + case CMD_READ_MULTIPLE_BLOCK: + case CMD_READ_SINGLE_BLOCK: + // TODO: introduce error code responses. + if (sd) { + logText("Reading from block %08x", cmd.address); + fseek(sd, cmd.address << kLog2BlockLen, SEEK_SET); + } + sendResponse(2u, 0xfe00); + reading = true; + readState = ReadState_CmdStatus; + readBytes = 0u; + break; + case CMD_WRITE_MULTIPLE_BLOCK: + case CMD_WRITE_SINGLE_BLOCK: + // TODO: introduce error code responses. + if (sd) { + logText("Writing to block %08x", cmd.address); + fseek(sd, cmd.address << kLog2BlockLen, SEEK_SET); + } + sendResponse(1u, 0xfe); + writing = true; + writeState = WriteState_StartToken; + writeBytes = 0u; + break; + case SD_SEND_OP_COND: + sendResponse(1, 0x00); + break; + case CMD_APP_CMD: + sendResponse(1, 0x00); + break; + case CMD_READ_OCR: + sendResponse(5u, 0); + break; + + default: + // Treat anything else as an illegal command. + sendResponse(1u, 0x05); + break; + } +} + +void spi_microsd::sendResponse(unsigned len, uint64_t d) { + responding = true; + rspBytes = 0u; + rspLen = len; + rsp[0] = (uint8_t)d; + rsp[1] = (uint8_t)(d >> 8); + rsp[2] = (uint8_t)(d >> 16); + rsp[3] = (uint8_t)(d >> 24); + rsp[4] = (uint8_t)(d >> 32); + rsp[5] = (uint8_t)(d >> 40); + rsp[6] = (uint8_t)(d >> 48); + rsp[7] = (uint8_t)(d >> 56); +} + +// Update the running CRC7 value for a series of bytes (command/response); +// used to generate the CRC for a command or check that of a response if CRC_ON mode is used. +// +// Note: the CRC7 is held in the MSBs of the byte, which should be set to 0 for the first +// invocation, and then the trailing LSB is always 1. +uint8_t spi_microsd::update_crc7(uint8_t crc, const uint8_t *data, size_t len) { + while (len-- > 0u) { + uint8_t d = *data++; + for (unsigned b = 0u; b < 8u; b++) { + crc = (crc << 1) ^ (((crc ^ d) & 0x80u) ? 0x12u : 0u); + d <<= 1; + } + } + // 7 MSBs contain the CRC residual and the trailing bit is 1. + return (crc | 1u); +} + +// Calculate the CRC16 value for a series of bytes (data blocks); +// used for generation or checking, if CRC_ON mode is used. +// +// Note: the CRC shall be zero for the first invocation. +uint16_t spi_microsd::update_crc16(uint16_t crc, const uint8_t *data, size_t len) { + while (len-- > 0u) { + uint16_t d = (uint16_t)*data++ << 8; + for (unsigned b = 0u; b < 8u; b++) { + crc = (crc << 1) ^ (((crc ^ d) & 0x8000u) ? 0x1021u : 0u); + d <<= 1; + } + } + return crc; +} diff --git a/dv/dpi/spidpi/spi_microsd.hh b/dv/dpi/spidpi/spi_microsd.hh new file mode 100644 index 000000000..36e559986 --- /dev/null +++ b/dv/dpi/spidpi/spi_microsd.hh @@ -0,0 +1,107 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include "spidpi.hh" + +// -------------------------- SPI microSD model ------------------------------- +class spi_microsd : public spidpi { +public: + spi_microsd(unsigned dataW, // Number of data lines. + unsigned oobInW, // Width of Out-Of-Band input data (bits). + unsigned oobOutW, // Width of Out-Of-Band output data (bits). + const char *sdFile, // Filename of the SD card image. + bool log = false) : // Enable diagnostic logging? + spidpi(dataW, oobInW, oobOutW, log) { + assert(sdFile); + logText("microSD model attempting to open image '%s'\n", sdFile); + sd = fopen(sdFile, "r+b"); + // If the open fails, it is not an error at this point; it is treated as analogous + // to there being no usable SD card present. + reset(); + } + +protected: + // Device reset. + virtual void reset(); + + // Write a byte to the SPI flash. + virtual void writeByte(uint8_t inByte, uint32_t oobIn); + + // Read a byte of data from the SPI flash. + virtual bool readByte(uint32_t oobIn, uint8_t &outByte, uint32_t &oobOut); + + // Change in the state of the CS line. + virtual void csChanged(bool csAsserted, uint32_t oobIn); + + // SD command received. + virtual void startCommand(); + + // Send response from SD card. + virtual void sendResponse(unsigned len, uint64_t d); // Up to 7 bytes. + +private: + // SPI mode uses only 512-byte blocks, so we choose this as the block size. + static constexpr unsigned kLog2BlockLen = 9u; + static constexpr unsigned kBlockLen = 1u << kLog2BlockLen; + + // SD card commands are 48 bits (6 bytes) with the data being transmitted MSB first. + struct { + uint8_t cmdCode; + uint32_t address; + uint8_t crc; + } cmd; + + // The number of bytes thus far received. + uint8_t cmdBytes; + + // Responding to a command? + bool responding; + // sendResponse can supply 8 bytes. + uint8_t rsp[8u]; + unsigned rspBytes; + unsigned rspLen; + + // Reading data from the microSD card? + bool reading; + enum { + ReadState_CmdStatus, + ReadState_DataBytes, + ReadState_CRC0, + ReadState_CRC1 + } readState; + // Number of bytes returned within the current data packet. + uint32_t readBytes; + // CRC16 for read data block. + uint16_t readCrc16; + + // Writing data to the microSD card? + bool writing; + enum { + WriteState_StartToken, + WriteState_DataBytes, + WriteState_CRC0, + WriteState_CRC1 + } writeState; + // Number of bytes written within the current data packet. + uint32_t writeBytes; + // Collected write data for validation before anything is written. + uint8_t writeBuf[kBlockLen]; + // CRC16 for write data block; updated as the bytes are received. + uint16_t writeCrc16; + + // Card Identification Data, including trailing CRC7 byte. + uint8_t cid[16]; + // Card Specific Data, including trailing CRC7 byte. + uint8_t csd[16]; + + // Storage medium (raw microSD card image; format and contents unspecified). + // If the file handle is NULL this means that there is no microSD card present. + FILE *sd; + + // Utility functions for performing a running update of CRC values. + static uint8_t update_crc7(uint8_t crc, const uint8_t *data, size_t len); + static uint16_t update_crc16(uint16_t crc, const uint8_t *data, size_t len); +}; diff --git a/dv/dpi/spidpi/spidpi.cc b/dv/dpi/spidpi/spidpi.cc index f545dec98..dae198724 100644 --- a/dv/dpi/spidpi/spidpi.cc +++ b/dv/dpi/spidpi/spidpi.cc @@ -6,8 +6,9 @@ #include #include -#include "spi_lcd.hh" #include "spi_flash.hh" +#include "spi_lcd.hh" +#include "spi_microsd.hh" void spidpi::reset() { // Write data to device (COPI). @@ -91,6 +92,8 @@ void *spidpi_create(const char *id, // Bus identification. ctx = new spi_flash(dataW, oobInW, oobOutW, 0xef4019); } else if (!strcmp(id, "lcd")) { ctx = new spi_lcd(dataW, oobInW, oobOutW); + } else if (!strcmp(id, "microsd")) { + ctx = new spi_microsd(dataW, oobInW, oobOutW, "sd.img"); } else if (!strcmp(id, "pmod_sf3")) { ctx = new spi_flash(dataW, oobInW, oobOutW, 0x20ba19); } else { diff --git a/dv/verilator/top_verilator.sv b/dv/verilator/top_verilator.sv index 8c390cf02..1bce3a8e0 100644 --- a/dv/verilator/top_verilator.sv +++ b/dv/verilator/top_verilator.sv @@ -121,17 +121,11 @@ module top_verilator (input logic clk_i, rst_ni); wire appspi_cs = out_to_pins[OUT_PIN_APPSPI_CS]; // microSD card interface. - // Note: no DPI model presently. - wire microsd_clk = out_to_pins[OUT_PIN_MICROSD_CLK]; - // SPI mode: CIPO - wire microsd_dat0 = 1'b1; - assign in_from_pins[IN_PIN_MICROSD_DAT0] = microsd_dat0; - // SPI mode: CS_N - wire microsd_dat3 = out_to_pins[OUT_PIN_MICROSD_DAT3]; - // SPI mode: COPI - wire microsd_cmd = out_to_pins[OUT_PIN_MICROSD_CMD]; - // pulled high to indicate the _absence_ of a microSD card. - wire microsd_det = 1'b1; + wire microsd_clk; // SPI mode: SCLK + wire microsd_dat0; // SPI mode: CIPO + wire microsd_dat3; // SPI mode: CS_N + wire microsd_cmd; // SPI mode: COPI + wire microsd_det; // microSD card detection; 0 = card present. // LCD interface. wire lcd_rst; @@ -154,7 +148,7 @@ module top_verilator (input logic clk_i, rst_ni); // None of these signals is used presently. wire unused_io_ = ^{mb1, ah_tmpio10, rph_g18, rph_g17, rph_g16_ce2, rph_g8_ce0, rph_g7_ce1, - usrLed, microsd_clk, microsd_cmd, microsd_dat3}; + usrLed}; // Reporting of CHERI enable/disable and any exceptions that occur. wire [CheriErrWidth-1:0] cheri_err; @@ -212,6 +206,12 @@ module top_verilator (input logic clk_i, rst_ni); assign uart_aux_tx = out_to_pins[OUT_PIN_SER1_TX]; assign rs485_tx = out_to_pins[OUT_PIN_RS485_TX]; + // Traffic to/from microSD card. + assign microsd_cmd = out_to_pins[OUT_PIN_MICROSD_CMD ]; + assign microsd_clk = out_to_pins[OUT_PIN_MICROSD_CLK ]; + assign microsd_dat3 = out_to_pins[OUT_PIN_MICROSD_DAT3]; + assign in_from_pins[IN_PIN_MICROSD_DAT0] = microsd_dat0; + // Output I2C traffic to the RPi HAT ID EEPROM. assign {scl_rpi0_o, scl_rpi0_oe} = {inout_to_pins[INOUT_PIN_RPH_G1], inout_to_pins_en[INOUT_PIN_RPH_G1]}; @@ -528,6 +528,25 @@ module top_verilator (input logic clk_i, rst_ni); .oob_out ( ) // not used. ); + // SPI connection to microSD card. + spidpi #( + .ID ("microsd"), + .NDevices (1), + .DataW (1), + .OOB_InW (1), + .OOB_OutW (1) + ) u_spidpi_microsd ( + .rst_ni (rst_ni), + + .sck (microsd_clk), + .cs (microsd_dat3), + .copi (microsd_cmd), + .cipo (microsd_dat0), + + .oob_in ( ), + .oob_out (microsd_det) + ); + // SPI connection to PMOD SF3 flash via PMOD1 pins spidpi #( .ID ("pmod_sf3"), diff --git a/sonata.core b/sonata.core index 914c916b9..9f98571ef 100644 --- a/sonata.core +++ b/sonata.core @@ -50,6 +50,8 @@ filesets: - dv/dpi/spidpi/spi_flash.hh: { file_type: cppSource, is_include_file: true } - dv/dpi/spidpi/spi_lcd.cc: { file_type: cppSource } - dv/dpi/spidpi/spi_lcd.hh: { file_type: cppSource, is_include_file: true } + - dv/dpi/spidpi/spi_microsd.cc: { file_type: cppSource } + - dv/dpi/spidpi/spi_microsd.hh: { file_type: cppSource, is_include_file: true } - dv/verilator/top_verilator.sv: { file_type: systemVerilogSource } - dv/verilator/sonata_system.cc: { file_type: cppSource } - dv/verilator/sonata_system.hh: { file_type: cppSource, is_include_file: true }