------------------------------------------------------------------------------- -- -- 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;