§ Описание

Модуль считывает с SD карты указанный в LBA сектор. При busy=0 строб 1 такт COMMAND=1 запускает выполнение считывания данных с SD-карты (если пин RW=0).
При наличии ошибки выдается ERROR != 0. Распознанный тип карты после чтения сектора находится в CARD. Строб сигнал DONE длиной в 1 такт сигнализирует о завершении считывания.
Пины A,I,O,W должны быть подключены к блочной памяти объемом 512 байт (размер сектора).

§ Код

module sd
(
    input               clock,                  // 25 MHZ
    input               reset_n,
    // Физический доступ
    output  reg         sclk,                   // Тактовая частота
    output  reg         cs,                     // =0 Chipselect =1 Disabled
    input               miso,                   // Из SD
    output  reg         mosi,                   // На SD
    // Управление устройством
    input               command,                // =1 Сигнал запуска
    input               rw,                     // =0 Чтение =1 Запись
    input       [31:0]  lba,                    // Запрошенный номер сектора с 0
    output  reg         busy,                   // =1 Устройство занято
    output  reg         done,                   // Строб-сигнал DONE (1 такт)
    output  reg [ 3:0]  error,                  // =1 Ошибка доступа к SD
    output  reg [ 1:0]  card,                   // Тип карты 0,1,2,3=SDHC
    // Чтение или запись в память данных
    output  reg [ 8:0]  a,
    output      [ 7:0]  i,
    output  reg [ 7:0]  o,
    output  reg         w
);

// ----------------------------------
localparam
    WAIT  = 0, ENSPI    = 1,
    INIT  = 2, COMMAND  = 3,
    FETCH = 4, READ     = 5,
    WRITE = 6;

// ----------------------------------

reg [ 4:0] t, r1, r2;
reg [ 7:0] c0, c1;
reg [ 1:0] c2;              // Делитель 4 тактов на передачу
reg [ 2:0] c3;              // Счетчик 8 тактов при передаче данных на SPI
reg [11:0] c4, c5;          // Для отсчета таймаутов
reg [17:0] timeout;
reg [31:0] arg;
reg [ 5:0] cmd;
reg [ 7:0] dw;

always @(posedge clock)
if (reset_n == 0) begin

    t       <= 0;
    busy    <= 0;
`ifdef ICARUS
    timeout <= 1;
`else
    timeout <= 0;
`endif

end else begin

    w    <= 0;
    done <= 0;

    case (t)

    // Ожидание получения команды
    WAIT: begin

        cs   <= 1;
        mosi <= 0;
        busy <= 0;
        sclk <= 0;
        card <= 0;

        // Сброс счетчиков
        {c0, c1, c2, c3, c4} <= 0;

        // Отчет таймаута простоя чипа
        timeout <= timeout ? timeout - 1 : 0;

        if (command) begin

            busy    <= 1;
            error   <= 0;
            t       <= timeout ? (rw ? WRITE : READ) : ENSPI;
            timeout <= 250000;

        end

    end

    // Отсчитать 80Т и выйти на процедуру инициализации [100 kHz]
    ENSPI: begin

        if (c0 == (250 >> 1) - 1) begin

            c0   <= 0;
            c1   <= c1 + 1;
            sclk <= ~sclk;

            if (c1 == 2*80-1) begin {c1, sclk} <= 0; t <= INIT; end

        end else c0 <= c0 + 1;

    end

    // Считывание байта DR из SPI и запись/чтение DW и вернуться на метку R2
    // Скорость 6.25 мгц (25:4)
    //      ___      ___         ___
    // ____/ 7 \____/ 6 \_....__/ 0 \__
    FETCH: case (c2)

        // CLK-DN
        0: begin c2 <= 1; sclk <= 0; end
        1: begin c2 <= 2; mosi <= dw[7]; end
        // CLK-UP
        2: begin c2 <= 3; sclk <= 1; end
        3: begin

            c2   <= 0;
            c3   <= c3 + 1;
            dw   <= {dw[6:0], miso};
            mosi <= 0;

            if (c3 == 7) begin t <= r2; sclk <= 0; end

        end

    endcase

    // Выполнение команды к SD, возврат на метку R1
    COMMAND: case (c1)

        // FOR i=0 TO 4095: IF (GET() == 0xFF) BREAK; NEXT i
        // При истечении таймаута выйти на WAIT с ошибкой
        0: begin c1 <= 1; cs <= 0; r2 <= COMMAND; c4 <= 4095; end
        1: begin c1 <= 2; dw <= 8'hFF; t <= FETCH; end
        2: begin

            c1 <= dw == 8'hFF ? 3 : 1;
            c4 <= c4 - 1;

            if (c4 == 0) begin error <= 1; t <= WAIT; end

        end
        // Отослать команду 8 бит
        3: begin t <= FETCH; dw <= {2'b01, cmd}; c1 <= 4; end
        // Отослать аргумент 32 бит
        4, 5, 6, 7: begin t <= FETCH; dw <= arg[31:24]; arg <= {arg[23:0], arg[31:24]}; c1 <= c1 + 1; end
        // Отсылка CRC
        8: begin

            t  <= FETCH;
            c1 <= 9;
            c4 <= 255;

            case (cmd) 0: dw <= 8'h95; 8: dw <= 8'h87; default: dw <= 8'hFF; endcase

        end
        // Ждать 256 проверок принятия команды
        // Тест флага BSY=1: в случае таймаута, ошибка #2
        9:  begin c1 <= 10; dw <= 8'hFF; t <= FETCH; end
        10: begin

            c1 <= dw[7] ? 9 : 0;
            c4 <= c4 - 1;

            if (c4 == 0) begin t <= WAIT; error <= 2; end
            else if (!dw[7]) t <= r1;

        end

    endcase

    // Отсылка команд на SD и вычисление типа карточки
    INIT: case (c0)

        // Отослать CMD0(0)
        0: begin c0 <= 1; r1 <= INIT; t <= COMMAND; {cmd, arg} <= 0; end
        // Проверить на R=01h, если неправильно, то ошибка #3
        1: begin

            c0  <= 2;
            cmd <= 8;
            arg <= 32'h01AA;

            if (dw != 8'h01) begin t <= WAIT; error <= 3; end
            else             begin t <= COMMAND; end

        end
        // Проверка бита 2 в ответе. Если там 1, то CARD1
        2: begin

            r2 <= INIT;
            c5 <= 4095;
            dw <= 8'hFF;

            if (dw[2])
                 begin c0 <= 7; card <= 1; end
            else begin c0 <= 3; t <= FETCH; end

        end
        // Дочитать 3 байта ответа
        3,4,5: begin c0 <= c0 + 1; dw <= 8'hFF; t <= FETCH; end
        // Если не AAh то ошибка карты #4; иначе это карта типа 2
        6: begin if (dw != 8'hAA) begin error <= 4; t <= WAIT; end else c0 <= 7; card <= 2; end
        // ACMD(0x40000000 или 0)
        7: begin c0 <= 8; t <= COMMAND; cmd <= 8'h37; arg <= 32'h0; end
        8: begin c0 <= 9; t <= COMMAND; cmd <= 8'h29; arg <= {card == 2 ? 8'h40 : 8'h00, 24'h0}; end
        // По истечении таймаута ошибка #5
        9: begin c0 <= dw ? 7 : 10; if (c5 == 0) begin error <= 5; t <= WAIT; end else c5 <= c5 - 1; end
        // Запрос SD2 дополнительной информации: CMD58(0)
        10: begin

            if (card == 2) begin c0 <= 11; t <= COMMAND; cmd <= 8'h3A; arg <= 0; end
            else           begin c0 <= 0;  t <= rw ? WRITE : READ; end

        end
        // Прочесть байт, проверить что есть старшие 2 бита и если есть, то это SDHC, и дочитать байты
        11: begin c0 <= 12; dw <= 8'hFF; t <= FETCH; r2 <= INIT; end
        12: begin c0 <= 13; dw <= 8'hFF; t <= FETCH; if (dw[7:6] == 2'b11) card <= 3; end
        13, 14: begin c0 <= c0 + 1; dw <= 8'hFF; t <= FETCH; end
        15: begin c0 <= 0; t <= rw ? WRITE : READ; end

    endcase

    // Чтение сектора
    READ: case (c0)

        // CMD17(LBA)
        0: begin c0 <= 1; r1 <= READ; t <= COMMAND; arg <= lba; cmd <= 17; end
        // Ожидать ответа 0xFE
        1: begin c0 <= 2; r2 <= READ; c4 <= 4095; end
        2: begin c0 <= 3; dw <= 8'hFF; t <= FETCH; end
        // Если FEh - OK, и если не FFh - ошибка #6, таймаут - ошибка #7
        3: begin

            if      (dw == 8'hFE) begin c0 <= 4; c4 <= 0; end
            else if (dw != 8'hFF) begin error <= 6; t <= WAIT; end
            else if (c4 == 8'h00) begin error <= 7; t <= WAIT; end
            else                  begin c0 <= 2; c4 <= c4 - 1; end

        end
        // Считывание 512 байт
        4: begin c0 <= 5; dw <= 8'hFF; t <= FETCH; end
        5: begin

            a   <= c4;
            w   <= 1;
            o   <= dw;
            c0  <= 4;
            c4  <= c4 + 1;

            if (c4 == 511) begin done <= 1; t <= WAIT; end

        end

    endcase

    endcase

end

endmodule