Apple-II_MiSTer/disk_ii.vhd

289 lines
9.2 KiB
VHDL

-------------------------------------------------------------------------------
--
-- Disk II emulator
--
-- This is read-only and only feeds "pre-nibblized" data to the processor
-- It has a single-track buffer and only supports one drive (1).
--
-- Stephen A. Edwards, sedwards@cs.columbia.edu
--
-------------------------------------------------------------------------------
--
-- Each track is represented as 0x1A00 bytes
-- Each disk image consists of 35 * 0x1A00 bytes = 0x38A00 (227.5 K)
--
-- X = $60 for slot 6
--
-- Off On
-- C080,X C081,X Phase 0 Head Stepper Motor Control
-- C082,X C083,X Phase 1
-- C084,X C085,X Phase 2
-- C086,X C087,X Phase 3
-- C088,X C089,X Motor On
-- C08A,X C08B,X Select Drive 2 (select drive 1 when off)
-- C08C,X C08D,X Q6 (Shift/load?)
-- C08E,X C08F,X Q7 (Write request to drive)
--
--
-- Q7 Q6
-- 0 0 Read
-- 0 1 Sense write protect
-- 1 0 Write
-- 1 1 Load Write Latch
--
-- Reading a byte:
-- LDA $C08E,X set read mode
-- ...
-- READ LDA $C08C,X
-- BPL READ
--
-- Sense write protect:
-- LDA $C08D,X
-- LDA $C08E,X
-- BMI PROTECTED
--
-- Writing
-- STA $C08F,X set write mode
-- ..
-- LDA DATA
-- STA $C08D,X load byte to write
-- STA $C08C,X write byte to disk
--
-- Data bytes must be written in 32 cycle loops.
--
-- There are 70 phases for the head stepper and and 35 tracks,
-- i.e., two phase changes per track.
--
-- The disk spins at 300 rpm; one new bit arrives every 4 us
-- The processor's clock is 1 MHz = 1 us, so it takes 8 * 4 = 32 cycles
-- for a new byte to arrive
--
-- This corresponds to dividing the 2 MHz signal by 64 to get the byte clock
--
-------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity disk_ii is
port (
CLK_14M : in std_logic;
CLK_2M : in std_logic;
PRE_PHASE_ZERO : in std_logic;
IO_SELECT : in std_logic; -- e.g., C600 - C6FF ROM
DEVICE_SELECT : in std_logic; -- e.g., C0E0 - C0EF I/O locations
RESET : in std_logic;
A : in unsigned(15 downto 0);
D_IN : in unsigned(7 downto 0); -- From 6502
D_OUT : out unsigned(7 downto 0); -- To 6502
TRACK : out unsigned(5 downto 0); -- Current track (0-34)
track_addr : out unsigned(13 downto 0);
D1_ACTIVE : out std_logic; -- Disk 1 motor on
D2_ACTIVE : out std_logic; -- Disk 2 motor on
ram_write_addr : in unsigned(12 downto 0); -- Address for track RAM
ram_di : in unsigned(7 downto 0); -- Data to track RAM
ram_we : in std_logic -- RAM write enable
);
end disk_ii;
architecture rtl of disk_ii is
signal motor_phase : std_logic_vector(3 downto 0);
signal drive_on : std_logic;
signal drive2_select : std_logic;
signal q6, q7 : std_logic;
signal rom_dout : unsigned(7 downto 0);
-- Current phase of the head. This is in half-steps to assign
-- a unique position to the case, say, when both phase 0 and phase 1 are
-- on simultaneously. phase(7 downto 2) is the track number
signal phase : unsigned(7 downto 0); -- 0 - 139
-- Storage for one track worth of data in "nibblized" form
type track_ram is array(0 to 6655) of unsigned(7 downto 0);
-- Double-ported RAM for holding a track
signal track_memory : track_ram;
signal ram_do : unsigned(7 downto 0);
-- Lower bit indicates whether disk data is "valid" or not
-- RAM address is track_byte_addr(14 downto 1)
-- This makes it look to the software like new data is constantly
-- being read into the shift register, which indicates the data is
-- not yet ready.
signal track_byte_addr : unsigned(14 downto 0);
signal read_disk : std_logic; -- When C08C accessed
begin
interpret_io : process (CLK_2M)
begin
if rising_edge(CLK_2M) then
if reset = '1' then
motor_phase <= (others => '0');
drive_on <= '0';
drive2_select <= '0';
q6 <= '0';
q7 <= '0';
else
if PRE_PHASE_ZERO = '1' and DEVICE_SELECT = '1' then
if A(3) = '0' then -- C080 - C087
motor_phase(TO_INTEGER(A(2 downto 1))) <= A(0);
else
case A(2 downto 1) is
when "00" => drive_on <= A(0); -- C088 - C089
when "01" => drive2_select <= A(0); -- C08A - C08B
when "10" => q6 <= A(0); -- C08C - C08D
when "11" => q7 <= A(0); -- C08E - C08F
when others => null;
end case;
end if;
end if;
end if;
end if;
end process;
D1_ACTIVE <= drive_on and not drive2_select;
D2_ACTIVE <= drive_on and drive2_select;
-- There are two cases:
--
-- Current phase is odd (between two poles)
-- |
-- V
-- -3-2-1 0 1 2 3
-- X X X X
-- 0 1 2 3
--
--
-- Current phase is even (under a pole)
-- |
-- V
-- -4-3-2-1 0 1 2 3 4
-- X X X X X
-- 0 1 2 3 0
--
update_phase : process (CLK_14M)
variable phase_change : integer;
variable new_phase : integer;
variable rel_phase : std_logic_vector(3 downto 0);
begin
if rising_edge(CLK_14M) then
if reset = '1' then
phase <= TO_UNSIGNED(70, 8); -- Deliberately odd to test reset
else
phase_change := 0;
new_phase := TO_INTEGER(phase);
rel_phase := motor_phase;
case phase(2 downto 1) is
when "00" =>
rel_phase := rel_phase(1 downto 0) & rel_phase(3 downto 2);
when "01" =>
rel_phase := rel_phase(2 downto 0) & rel_phase(3);
when "10" => null;
when "11" =>
rel_phase := rel_phase(0) & rel_phase(3 downto 1);
when others => null;
end case;
if phase(0) = '1' then -- Phase is odd
case rel_phase is
when "0000" => phase_change := 0;
when "0001" => phase_change := -3;
when "0010" => phase_change := -1;
when "0011" => phase_change := -2;
when "0100" => phase_change := 1;
when "0101" => phase_change := -1;
when "0110" => phase_change := 0;
when "0111" => phase_change := -1;
when "1000" => phase_change := 3;
when "1001" => phase_change := 0;
when "1010" => phase_change := 1;
when "1011" => phase_change := -3;
when "1111" => phase_change := 0;
when others => null;
end case;
else -- Phase is even
case rel_phase is
when "0000" => phase_change := 0;
when "0001" => phase_change := -2;
when "0010" => phase_change := 0;
when "0011" => phase_change := -1;
when "0100" => phase_change := 2;
when "0101" => phase_change := 0;
when "0110" => phase_change := 1;
when "0111" => phase_change := 0;
when "1000" => phase_change := 0;
when "1001" => phase_change := 1;
when "1010" => phase_change := 2;
when "1011" => phase_change := -2;
when "1111" => phase_change := 0;
when others => null;
end case;
end if;
if new_phase + phase_change <= 0 then
new_phase := 0;
elsif new_phase + phase_change > 139 then
new_phase := 139;
else
new_phase := new_phase + phase_change;
end if;
phase <= TO_UNSIGNED(new_phase, 8);
end if;
end if;
end process;
TRACK <= phase(7 downto 2);
-- Dual-ported RAM holding the contents of the track
track_storage : process (CLK_14M)
begin
if rising_edge(CLK_14M) then
if ram_we = '1' then
track_memory(to_integer(ram_write_addr)) <= ram_di;
end if;
ram_do <= track_memory(to_integer(track_byte_addr(14 downto 1)));
end if;
end process;
-- Go to the next byte when the disk is accessed or if the counter times out
read_head : process (CLK_2M)
variable byte_delay : unsigned(5 downto 0); -- Accounts for disk spin rate
begin
if rising_edge(CLK_2M) then
if reset = '1' then
track_byte_addr <= (others => '0');
byte_delay := (others => '0');
else
byte_delay := byte_delay - 1;
if (read_disk = '1' and PRE_PHASE_ZERO = '1') or byte_delay = 0 then
byte_delay := (others => '0');
if track_byte_addr = X"33FE" then
track_byte_addr <= (others => '0');
else
track_byte_addr <= track_byte_addr + 1;
end if;
end if;
end if;
end if;
end process;
rom : entity work.disk_ii_rom port map (
addr => A(7 downto 0),
clk => CLK_14M,
dout => rom_dout);
read_disk <= '1' when DEVICE_SELECT = '1' and A(3 downto 0) = x"C" else
'0'; -- C08C
D_OUT <= rom_dout when IO_SELECT = '1' else
ram_do when read_disk = '1' and track_byte_addr(0) = '0' else
(others => '0');
track_addr <= track_byte_addr(14 downto 1);
end rtl;