Apple_II_vhdl/src/spi_controller.vhd

472 lines
17 KiB
VHDL

-------------------------------------------------------------------------------
--
-- SD/MMC interface (SPI-style) for the Apple ][ Emulator
--
-- Michel Stempin (michel.stempin@wanadoo.fr)
-- Working with MMC/SD/SDHC cards
--
-- From previous work by:
-- Stephen A. Edwards (sedwards@cs.columbia.edu)
--
-------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity spi_controller is
generic (
BLOCK_SIZE : natural := 512;
BLOCK_BITS : natural := 9;
TRACK_SIZE : natural := 16#1A00#
);
port (
-- Card Interface ---------------------------------------------------------
CS_N : out std_logic; -- MMC chip select
MOSI : out std_logic; -- Data to card (master out slave in)
MISO : in std_logic; -- Data from card (master in slave out)
SCLK : out std_logic; -- Card clock
-- Track buffer Interface -------------------------------------------------
ram_write_addr : out unsigned(13 downto 0);
ram_di : out unsigned(7 downto 0);
ram_we : out std_logic;
track : in unsigned(5 downto 0); -- Track number (0-34)
image : in unsigned(9 downto 0); -- Which disk image to read
-- System Interface -------------------------------------------------------
CLK_14M : in std_logic; -- System clock
reset : in std_logic
);
end spi_controller;
architecture rtl of spi_controller is
-----------------------------------------------------------------------------
-- States of the combined MMC/SD/SDHC reset, track and command FSM
--
type states is (
-- Reset FSM
POWER_UP,
RAMP_UP,
CHECK_CMD0,
CHECK_CMD8,
CHECK_CMD55,
CHECK_ACMD41,
CHECK_CMD1,
CHECK_CMD58,
CHECK_SET_BLOCKLEN,
ERROR,
-- Track read FSM
IDLE,
READ_TRACK,
READ_BLOCK_WAIT,
READ_BLOCK_DATA,
READ_BLOCK_CRC,
-- SD command embedded FSM
WAIT_NRC,
SEND_CMD,
RECEIVE_BYTE_WAIT,
RECEIVE_BYTE);
--
signal state, return_state : states;
--
-----------------------------------------------------------------------------
signal slow_clk : boolean := true;
signal spi_clk : std_logic;
signal sclk_sig : std_logic;
signal current_track : unsigned(5 downto 0);
signal current_image : unsigned(9 downto 0);
signal write_addr : unsigned(13 downto 0);
signal command : std_logic_vector(5 downto 0);
signal argument : std_logic_vector(31 downto 0);
signal crc7 : std_logic_vector(6 downto 0);
signal command_out : std_logic_vector(55 downto 0);
signal recv_bytes : unsigned(39 downto 0);
type versions is (MMC, SD1x, SD2x);
signal version : versions;
signal high_capacity : boolean;
begin
ram_write_addr <= write_addr;
-----------------------------------------------------------------------------
-- Process var_clkgen
--
-- Purpose:
-- Implements the variable speed clock for MMC compatibility.
-- If slow_clk is false, spi_clk == CLK_14M, thus SCLK = 7M
-- If slow_clk is true, spi_clk = CLK_14M / 32 and SCLK = 223.214kHz, which
-- is between 100kHz and 400kHz, as required for MMC compatibility.
--
var_clkgen : process (CLK_14M, slow_clk)
variable var_clk : unsigned(4 downto 0) := (others => '0');
begin
if slow_clk then
spi_clk <= var_clk(4);
if rising_edge(CLK_14M) then
var_clk := var_clk + 1;
end if;
else
spi_clk <= CLK_14M;
end if;
end process;
SCLK <= sclk_sig;
--
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Process sd_fsm
--
-- Purpose:
-- Implements the combined "SD Card init", "track read" and "command" FSMs.
--
sd_fsm : process(spi_clk)
subtype cmd_t is std_logic_vector(5 downto 0);
constant CMD0 : cmd_t := std_logic_vector(to_unsigned(0, 6));
constant CMD1 : cmd_t := std_logic_vector(to_unsigned(1, 6));
constant CMD8 : cmd_t := std_logic_vector(to_unsigned(8, 6));
constant CMD16 : cmd_t := std_logic_vector(to_unsigned(16, 6));
constant CMD17 : cmd_t := std_logic_vector(to_unsigned(17, 6));
constant CMD55 : cmd_t := std_logic_vector(to_unsigned(55, 6));
constant CMD58 : cmd_t := std_logic_vector(to_unsigned(58, 6));
constant ACMD41 : cmd_t := std_logic_vector(to_unsigned(41, 6));
variable counter : unsigned(7 downto 0);
variable byte_counter : unsigned(BLOCK_BITS - 1 downto 0);
variable lba : unsigned(31 downto 0);
begin
if rising_edge(spi_clk) then
ram_we <= '0';
if reset = '1' then
state <= POWER_UP;
-- Deliberately out of range
current_track <= (others => '1');
current_image <= (others => '1');
sclk_sig <= '0';
slow_clk <= true;
CS_N <= '1';
command <= (others => '0');
argument <= (others => '0');
crc7 <= (others => '0');
command_out <= (others => '1');
counter := TO_UNSIGNED(0, 8);
byte_counter := TO_UNSIGNED(0, BLOCK_BITS);
write_addr <= (others => '0');
high_capacity <= false;
version <= MMC;
lba := (others => '0');
else
case state is
---------------------------------------------------------------------
-- SD Card init FSM
---------------------------------------------------------------------
when POWER_UP =>
counter := TO_UNSIGNED(224, 8);
state <= RAMP_UP;
-- Output a series of 74 clock signals (or 1ms delay, whichever is
-- greater) to wake up the card
when RAMP_UP =>
if counter = 0 then
CS_N <= '0';
command <= CMD0;
argument <= (others => '0');
crc7 <= "1001010";
return_state <= CHECK_CMD0;
state <= WAIT_NRC;
else
counter := counter - 1;
sclk_sig <= not sclk_sig;
end if;
-- CMD0: GO_IDLE_STATE ----------------------------------------------
when CHECK_CMD0 =>
if recv_bytes(7 downto 0) = x"01" then
command <= CMD8;
-- Propose 2.7-3.6V operating voltage and a "10101010" test pattern
argument <= x"000001aa";
crc7 <= "1000011";
return_state <= CHECK_CMD8;
state <= WAIT_NRC;
else
state <= ERROR;
end if;
-- CMD8: SEND_IF_COND -----------------------------------------------
when CHECK_CMD8 =>
argument <= (others => '0');
crc7 <= (others => '0');
if recv_bytes(39 downto 32) <= x"01" then
-- This is an SD 2.x/3.x Card
version <= SD2x;
if recv_bytes(11 downto 8) /= "0001" or recv_bytes(7 downto 0) /= x"aa" then
-- Operating voltage or pattern check failure
state <= ERROR;
else
command <= CMD55;
high_capacity <= true;
return_state <= CHECK_CMD55;
state <= WAIT_NRC;
end if;
else
-- This is an MMC Card or an SD 1.x Card
version <= SD1x;
high_capacity <= false;
command <= CMD55;
return_state <= CHECK_CMD55;
state <= WAIT_NRC;
end if;
-- CMD55: APP_CMD ---------------------------------------------------
when CHECK_CMD55 =>
if recv_bytes(7 downto 0) = x"01" then
-- This is an SD Card
command <= ACMD41;
if high_capacity then
-- Ask for HCS (High Capacity Support)
argument <= x"40000000";
end if;
return_state <= CHECK_ACMD41;
state <= WAIT_NRC;
else
-- This is an MMC Card
version <= MMC;
command <= CMD1;
return_state <= CHECK_CMD1;
state <= WAIT_NRC;
end if;
-- ACMD41: SEND_OP_CMD (SD Card) ------------------------------------
when CHECK_ACMD41 =>
if recv_bytes(7 downto 0) = x"00" then
if version = SD2x then
-- This is an SD 2.x/3.x Card, read OCR
command <= CMD58;
argument <= (others => '0');
return_state <= CHECK_CMD58;
state <= WAIT_NRC;
else
-- This is an SD 1.x Card, no HCS
command <= CMD16;
argument <= std_logic_vector(to_unsigned(BLOCK_SIZE, 32));
return_state <= CHECK_SET_BLOCKLEN;
state <= WAIT_NRC;
end if;
elsif recv_bytes(7 downto 0) = x"01" then
-- Wait until the card goes out of idle state
command <= CMD55;
argument <= (others => '0');
return_state <= CHECK_CMD55;
state <= WAIT_NRC;
else
-- Found an MMC card that understands CMD55, but not ACMD41
command <= CMD1;
return_state <= CHECK_CMD1;
state <= WAIT_NRC;
end if;
-- CMD1: SEND_OP_CMD (MMC Card) -------------------------------------
when CHECK_CMD1 =>
if recv_bytes(7 downto 0) <= x"01" then
command <= CMD16;
argument <= std_logic_vector(to_unsigned(BLOCK_SIZE, 32));
return_state <= CHECK_SET_BLOCKLEN;
state <= WAIT_NRC;
else
-- Wait until the card goes out of idle state
command <= CMD1;
return_state <= CHECK_CMD1;
state <= WAIT_NRC;
end if;
-- CMD58: READ_OCR --------------------------------------------------
when CHECK_CMD58 =>
if recv_bytes(7 downto 0) = x"00" then
if recv_bytes(30) = '1' then
high_capacity <= true;
else
high_capacity <= false;
end if;
command <= CMD16;
argument <= std_logic_vector(to_unsigned(BLOCK_SIZE, 32));
return_state <= CHECK_SET_BLOCKLEN;
state <= WAIT_NRC;
else
state <= ERROR;
end if;
-- CMD16: SET_BLOCKLEN (BLOCK_SIZE) ---------------------------------
when CHECK_SET_BLOCKLEN =>
if recv_bytes(7 downto 0) = x"00" then
slow_clk <= false;
state <= IDLE;
else
state <= ERROR;
end if;
-- Error state ------------------------------------------------------
when ERROR =>
sclk_sig <= '0';
slow_clk <= true;
CS_N <= '1';
---------------------------------------------------------------------
-- Embedded "read track" FSM
---------------------------------------------------------------------
-- Idle state where we sit waiting for user image/track requests ----
when IDLE =>
if track /= current_track or image /= current_image then
-- Compute the LBA (Logical Block Address) from the given
-- image/track numbers.
-- Each Apple ][ floppy image contains 35 tracks, each consisting of
-- 16 x 256-byte sectors.
-- However, because of inter-sector gaps and address fields in
-- raw mode, the actual length is set to 0x1A00, so each image is
-- actually $1A00 bytes * 0x23 tracks = 0x38E00 bytes.
-- So: lba = image * 0x38E00 + track * 0x1A00
-- In order to avoid multiplications by constants, we replace
-- them by direct add/sub of shifted image/track values:
-- 0x38E00 = 0011 1000 1110 0000 0000
-- = 0x40000 - 0x8000 + 0x1000 - 0x200
-- 0x01A00 = 0000 0001 1010 0000 0000
-- = 0x1000 + 0x800 + 0x200
lba := ("0000" & image & "000000000000000000") -
( image & "000000000000000") +
( image & "000000000000") -
( image & "000000000") +
( track & "000000000") +
( track & "00000000000") +
( track & "000000000000");
if high_capacity then
-- For SDHC, blocks are addressed by blocks, not bytes
lba := lba srl BLOCK_BITS;
end if;
write_addr <= (others => '0');
CS_N <= '0';
state <= READ_TRACK;
current_track <= track;
current_image <= image;
else
CS_N <= '1';
sclk_sig <= '1';
end if;
-- Read in a whole track into buffer memory -------------------------
when READ_TRACK =>
if write_addr = TRACK_SIZE then
state <= IDLE;
else
command <= CMD17;
argument <= std_logic_vector(lba);
return_state <= READ_BLOCK_WAIT;
state <= WAIT_NRC;
end if;
-- Wait for a 0 bit to signal the start of the block ----------------
when READ_BLOCK_WAIT =>
if sclk_sig = '1' and MISO = '0' then
state <= READ_BLOCK_DATA;
byte_counter := TO_UNSIGNED(BLOCK_SIZE - 1, BLOCK_BITS);
counter := TO_UNSIGNED(7, 8);
return_state <= READ_BLOCK_DATA;
state <= RECEIVE_BYTE;
end if;
sclk_sig <= not sclk_sig;
-- Read a block of data ---------------------------------------------
when READ_BLOCK_DATA =>
ram_we <= '1';
write_addr <= write_addr + 1;
if byte_counter = 0 then
counter := TO_UNSIGNED(7, 8);
return_state <= READ_BLOCK_CRC;
state <= RECEIVE_BYTE;
else
byte_counter := byte_counter - 1;
counter := TO_UNSIGNED(7, 8);
return_state <= READ_BLOCK_DATA;
state <= RECEIVE_BYTE;
end if;
-- Read the block CRC -----------------------------------------------
when READ_BLOCK_CRC =>
counter := TO_UNSIGNED(7, 8);
return_state <= READ_TRACK;
if high_capacity then
lba := lba + 1;
else
lba := lba + BLOCK_SIZE;
end if;
state <= RECEIVE_BYTE;
---------------------------------------------------------------------
-- Embedded "command" FSM
---------------------------------------------------------------------
-- Wait for card response in front of host command ------------------
when WAIT_NRC =>
counter := TO_UNSIGNED(63, 8);
command_out <= "11111111" & "01" & command & argument & crc7 & "1";
sclk_sig <= not sclk_sig;
state <= SEND_CMD;
-- Send a command to the card ---------------------------------------
when SEND_CMD =>
if sclk_sig = '1' then
if counter = 0 then
state <= RECEIVE_BYTE_WAIT;
else
counter := counter - 1;
command_out <= command_out(54 downto 0) & "1";
end if;
end if;
sclk_sig <= not sclk_sig;
-- Wait for a "0", indicating the first bit of a response -----------
when RECEIVE_BYTE_WAIT =>
if sclk_sig = '1' then
if MISO = '0' then
recv_bytes <= (others => '0');
if command = CMD8 or command = CMD58 then
-- This is an R7 response, but we already have read bit 39
counter := TO_UNSIGNED(38,8);
else
-- This is a data byte or an r1 response, but we already read
-- bit 7
counter := TO_UNSIGNED(6, 8);
end if;
state <= RECEIVE_BYTE;
end if;
end if;
sclk_sig <= not sclk_sig;
-- Receive a byte ---------------------------------------------------
when RECEIVE_BYTE =>
if sclk_sig = '1' then
recv_bytes <= recv_bytes(38 downto 0) & MISO;
if counter = 0 then
state <= return_state;
ram_di <= recv_bytes(6 downto 0) & MISO;
else
counter := counter - 1;
end if;
end if;
sclk_sig <= not sclk_sig;
when others => null;
end case;
end if;
end if;
end process sd_fsm;
MOSI <= command_out(55);
end rtl;