From b77730fcec9cbafc6f5706fd94b5f28e8bc5828e Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 5 Jan 2016 13:40:50 +0100 Subject: [PATCH] add more source --- CMakeLists.txt | 11 + README.md | 17 +- make_roms.sh | 38 +++ src/Debouncer.vhd | 45 ++++ src/dac.vhd | 71 ++++++ src/disk_ii.vhd | 288 ++++++++++++++++++++++ src/grp_debouncer.vhd | 204 ++++++++++++++++ src/keyboard_apple.vhd | 502 +++++++++++++++++++++++++++++++++++++++ src/papilio_duo.ucf | 112 +++++++++ src/spi_controller.vhd | 471 ++++++++++++++++++++++++++++++++++++ src/timing_generator.vhd | 174 ++++++++++++++ src/timing_testbench.vhd | 25 ++ src/vga_controller.vhd | 326 +++++++++++++++++++++++++ tools/CMakeLists.txt | 3 + tools/dsk2nib.c | 228 ++++++++++++++++++ 15 files changed, 2514 insertions(+), 1 deletion(-) create mode 100644 CMakeLists.txt create mode 100755 make_roms.sh create mode 100644 src/Debouncer.vhd create mode 100644 src/dac.vhd create mode 100644 src/disk_ii.vhd create mode 100644 src/grp_debouncer.vhd create mode 100644 src/keyboard_apple.vhd create mode 100644 src/papilio_duo.ucf create mode 100644 src/spi_controller.vhd create mode 100644 src/timing_generator.vhd create mode 100644 src/timing_testbench.vhd create mode 100644 src/vga_controller.vhd create mode 100644 tools/CMakeLists.txt create mode 100644 tools/dsk2nib.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..577074a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required (VERSION 2.8) + +project (Tools) +find_package(PkgConfig) + +include(CheckIncludeFiles) +add_subdirectory(tools) + + + + diff --git a/README.md b/README.md index 5003a96..382aa96 100644 --- a/README.md +++ b/README.md @@ -1 +1,16 @@ -# Apple_II_vhdl +This is a reconstruction of an 1980s-era Apple ][+ implemented in VHDL for +FPGAs. + +The source code is copied from + +Stephen A. Edwards, sedwards@cs.columbia.edu +http://www1.cs.columbia.edu/~sedwards + + + +Build ToDO + + +- Download the Apple II roms from the web +- + diff --git a/make_roms.sh b/make_roms.sh new file mode 100755 index 0000000..0da1e68 --- /dev/null +++ b/make_roms.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +# SHA1 checksum +export rom_path_src=./roms +export rom_path=./build +#export tools_path=../build2/romgen + +export romgen_path=./romgen_src +#mkdir $rom_path + +sha1sum $rom_path_src/apple_II.rom +sha1sum $rom_path_src/APPLE2.ROM +sha1sum $rom_path_src/bios.rom + +#341-011-D0 Applesoft BASIC D0 +#341-012-D8 Applesoft BASIC D8 +#341-013-E0 Applesoft BASIC E0 +#341-014-E8 Applesoft BASIC E8 +#341-015-F0 Applesoft BASIC F0 +#341-020-F8 Autostart Monitor + + +rm $rom_path_src/apple_II_auto.bin + +# autostart rom +cat $rom_path_src/341011d0.bin $rom_path_src/341012d8.bin $rom_path_src/341013e0.bin $rom_path_src/341014e8.bin $rom_path_src/341015f0.bin $rom_path_src/341020f8.bin >> $rom_path_src/apple_II_auto.bin + +$romgen_path/romgen $rom_path_src/apple_II_auto.bin apple_II_auto_rom 14 a r > $rom_path/apple_II_auto_rom.vhd + +$romgen_path/romgen $rom_path_src/apple_II.rom apple_II_rom 14 a r > $rom_path/apple_II_rom.vhd + +$romgen_path/romgen $rom_path_src/APPLE2.ROM APPLE2_ROM 14 a r > $rom_path/APPLE2_ROM.vhd + +# test rom +$romgen_path/romgen $rom_path_src/bios.rom bios_rom 14 a r > $rom_path/bios_rom.vhd + + + diff --git a/src/Debouncer.vhd b/src/Debouncer.vhd new file mode 100644 index 0000000..6ef0d4d --- /dev/null +++ b/src/Debouncer.vhd @@ -0,0 +1,45 @@ +-- (C) Rui T. Sousa from http://sweet.ua.pt/~a16360 + +library IEEE; +use IEEE.STD_LOGIC_1164.ALL; +use IEEE.STD_LOGIC_ARITH.ALL; +use IEEE.STD_LOGIC_UNSIGNED.ALL; + +entity Debouncer is + generic (Delay : positive); + port ( + Clock : in STD_LOGIC; + Reset : in STD_LOGIC; + Input : in STD_LOGIC; + Output : out STD_LOGIC + ); +end Debouncer; + +architecture Behavioral of Debouncer is + + signal DelayCounter : natural range 0 to Delay; + signal Internal : STD_LOGIC; + +begin + + process(Clock, Reset) + begin + if rising_edge(Clock) then + if Reset = '1' then + Output <= '0'; + Internal <= '0'; + DelayCounter <= 0; + else + if Input /= Internal then + Internal <= Input; + DelayCounter <= 0; + elsif DelayCounter = Delay then + Output <= Internal; + else + DelayCounter <= DelayCounter + 1; + end if; + end if; + end if; + end process; + +end Behavioral; \ No newline at end of file diff --git a/src/dac.vhd b/src/dac.vhd new file mode 100644 index 0000000..c21b306 --- /dev/null +++ b/src/dac.vhd @@ -0,0 +1,71 @@ +------------------------------------------------------------------------------- +-- +-- Delta-Sigma DAC +-- +-- $Id: dac.vhd,v 1.1 2005/10/25 21:09:42 arnim Exp $ +-- +-- Refer to Xilinx Application Note XAPP154. +-- +-- This DAC requires an external RC low-pass filter: +-- +-- dac_o 0---XXXXX---+---0 analog audio +-- 3k3 | +-- === 4n7 +-- | +-- GND +-- +------------------------------------------------------------------------------- + +library ieee; +use ieee.std_logic_1164.all; + +entity dac is + + generic ( + msbi_g : integer := 7 + ); + port ( + clk_i : in std_logic; + res_n_i : in std_logic; + dac_i : in std_logic_vector(msbi_g downto 0); + dac_o : out std_logic + ); + +end dac; + +library ieee; +use ieee.numeric_std.all; + +architecture rtl of dac is + + signal DACout_q : std_logic; + signal DeltaAdder_s, + SigmaAdder_s, + SigmaLatch_q, + DeltaB_s : unsigned(msbi_g+2 downto 0); + +begin + + DeltaB_s(msbi_g+2 downto msbi_g+1) <= SigmaLatch_q(msbi_g+2) & + SigmaLatch_q(msbi_g+2); + DeltaB_s(msbi_g downto 0) <= (others => '0'); + + DeltaAdder_s <= unsigned('0' & '0' & dac_i) + DeltaB_s; + + SigmaAdder_s <= DeltaAdder_s + SigmaLatch_q; + + seq: process (clk_i, res_n_i) + begin + if res_n_i = '0' then + SigmaLatch_q <= to_unsigned(2**(msbi_g+1), SigmaLatch_q'length); + DACout_q <= '0'; + + elsif clk_i'event and clk_i = '1' then + SigmaLatch_q <= SigmaAdder_s; + DACout_q <= SigmaLatch_q(msbi_g+2); + end if; + end process seq; + + dac_o <= DACout_q; + +end rtl; diff --git a/src/disk_ii.vhd b/src/disk_ii.vhd new file mode 100644 index 0000000..8497abf --- /dev/null +++ b/src/disk_ii.vhd @@ -0,0 +1,288 @@ +------------------------------------------------------------------------------- +-- +-- 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(13 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; diff --git a/src/grp_debouncer.vhd b/src/grp_debouncer.vhd new file mode 100644 index 0000000..a95a2f6 --- /dev/null +++ b/src/grp_debouncer.vhd @@ -0,0 +1,204 @@ +----------------------------------------------------------------------------------------------------------------------- +-- Author: Jonny Doin, jdoin@opencores.org, jonnydoin@gmail.com +-- +-- Create Date: 09:56:30 07/06/2011 +-- Module Name: grp_debouncer - RTL +-- Project Name: basic functions +-- Target Devices: Spartan-6 +-- Tool versions: ISE 13.1 +-- Description: +-- +-- This block is a generic multiple input debouncing circuit. +-- It handles multiple inputs, like mechanical switch inputs, and outputs a debounced, stable registered version of the inputs. +-- A 'new_data' one-cycle strobe is also available, to sync downstream logic. +-- +-- CONCEPTUAL CIRCUIT +-- ================== +-- +-- W +-- /----------------/----------------\ +-- | | +-- | | +-- | ______ ______ | _____ +-- | W | | W |fdr | W | W |cmp \ +-- \----/---| +1 |---/----| |--/--+----/----| \ +-- | | | | | \ +-- ------ | | \ | +-- | | | = |-----\ +-- |> R | / | | +-- ---+-- | / | +-- | CNT_VAL---| / | +-- | |____/ | +-- | | +-- \------------\ | +-- | | +-- N ____ | | +-- /-------/---)) \ ____ | | +-- | ))XOR |-----) \ | | +-- | /------))___/ )OR |-----/ | +-- | | /---)___/ | +-- | | | | +-- | | \----------\ | +-- | | N | | +-- | \--------/-----------\ +----------------------+---------\ +-- | | | | +-- \---\ | | | +-- ______ | ______ | | ______ | +-- | fd | | | fd | | | |fde | | +-- [data_i]----/-----| |---/---+---/----| |---/---+----)---| |---/---+---/-----------)------------------------[data_o] +-- N | | N N | | N | | | | N | N | +-- | | | | | \---|CE | | | +-- | | | | | | | | | +-- [clk_i]----> |> | |> | | |> | | | ____ ______ +-- ------ ------ | ------ | N ____ \---| \ | fd | +-- | \---/---)) \ |AND |-----| |----[strb_o] +-- | ))XOR |-----|___/ | | +-- \-------------------------/---))___/ | | +-- N | | +-- |> | +-- ------ +-- +-- +-- PIPELINE LOGIC +-- ============== +-- +-- This debouncer circuit detects edges in an input signal, and waits the signal to stabilize for the designated time +-- before transferring the stable signal to the registered output. +-- A one-clock-cyle strobe is pulsed at the output to signalize a new data available. +-- The core clock should be the system clock, to optimize use of global clock resources. +-- +-- GROUP DEBOUNCING +-- ================ +-- +-- A change in state in any bit in the input word causes reload of the delay counter, and the output word is updated only +-- when all bits are stable for the specified period. Therefore, the grouping of signals and delay selection should match +-- behaviour of the selected signals. +-- +-- RESOURCES USED +-- ============== +-- +-- The number of registers inferred is: 3*N + (LOG(CNT_VAL)/LOG(2)) + 1 registers. +-- The number of LUTs inferred is roughly: ((4*N+2)/6)+2. +-- The slice distribution will vary, and depends on the control set restrictions and LUT-FF pairs resulting from map+p&r. +-- +-- This design was originally targeted to a Spartan-6 platform, synthesized with XST and normal constraints. +-- Verification in silicon was done on a Digilent Atlys board with a Spartan-6 FPGA @100MHz clock. +-- The VHDL dialect used is VHDL'93, accepted largely by all synthesis tools. +-- +------------------------------ COPYRIGHT NOTICE ----------------------------------------------------------------------- +-- +-- +-- Author(s): Jonny Doin, jdoin@opencores.org, jonnydoin@gmail.com +-- +-- Copyright (C) 2011 Jonny Doin +-- ----------------------------- +-- +-- This source file may be used and distributed without restriction provided that this copyright statement is not +-- removed from the file and that any derivative work contains the original copyright notice and the associated +-- disclaimer. +-- +-- This source file is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser +-- General Public License as published by the Free Software Foundation; either version 2.1 of the License, or +-- (at your option) any later version. +-- +-- This source is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +-- warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +-- details. +-- +-- You should have received a copy of the GNU Lesser General Public License along with this source; if not, download +-- it from http://www.gnu.org/licenses/lgpl.txt +-- +------------------------------ REVISION HISTORY ----------------------------------------------------------------------- +-- +-- 2011/07/06 v0.01.0010 [JD] started development. verification of synthesis circuit inference. +-- 2011/07/07 v1.00.0020 [JD] verification in silicon. operation at 100MHz, tested on the Atlys board (Spartan-6 LX45). +-- 2011/08/10 v1.01.0025 [JD] added one pipeline delay to new data strobe output. +-- 2011/09/19 v1.01.0030 [JD] changed range for internal counter (cnt_reg, cnt_next) to avoid adder flipover (Altera/ModelSim). +-- +----------------------------------------------------------------------------------------------------------------------- +-- TODO +-- ==== +-- +-- The circuit can easily be extended to have a signature of which inputs changed at the data out port. +-- +----------------------------------------------------------------------------------------------------------------------- +library ieee; +use ieee.std_logic_1164.all; + +entity grp_debouncer is + Generic ( + N : positive := 8; -- input bus width + CNT_VAL : positive := 10000); -- clock counts for debounce period + Port ( + clk_i : in std_logic := 'X'; -- system clock + data_i : in std_logic_vector (N-1 downto 0) := (others => 'X'); -- noisy input data + data_o : out std_logic_vector (N-1 downto 0); -- registered stable output data + strb_o : out std_logic -- strobe for new data available + ); +end grp_debouncer; + +architecture rtl of grp_debouncer is + -- datapath pipeline + signal reg_A, reg_B : std_logic_vector (N-1 downto 0) := (others => '0'); -- debounce edge detectors + signal reg_out : std_logic_vector (N-1 downto 0) := (others => '0'); -- registered output + signal dat_strb : std_logic := '0'; -- data transfer strobe + signal strb_reg : std_logic := '0'; -- registered strobe + signal strb_next : std_logic := '0'; -- lookahead strobe + signal dat_diff : std_logic := '0'; -- edge detector + -- debounce counter + signal cnt_reg : integer range CNT_VAL + 1 downto 0 := 0; -- debounce period counter + signal cnt_next : integer range CNT_VAL + 1 downto 0 := 0; -- combinatorial signal +begin + + --============================================================================================= + -- DEBOUNCE COUNTER LOGIC + --============================================================================================= + -- This counter is implemented as a up-counter with reset and final count detection via compare, + -- instead of a down-counter with preset and final count detection via nonzero detection. + -- This is better for Spartan-6 and Virtex-6 CLB architecture, because it uses less control sets. + -- + -- cnt_reg register transfer logic + cnt_reg_proc: process (clk_i) is + begin + if clk_i'event and clk_i = '1' then + cnt_reg <= cnt_next; + end if; + end process cnt_reg_proc; + -- cnt_next combinatorial logic + cnt_next_proc: cnt_next <= 0 when dat_diff = '1' or dat_strb = '1' else cnt_reg + 1; + -- final count combinatorial logic + final_cnt_proc: dat_strb <= '1' when cnt_reg = CNT_VAL else '0'; + + --============================================================================================= + -- DATAPATH SIGNAL PIPELINE + --============================================================================================= + -- input pipeline logic + pipeline_proc: process (clk_i) is + begin + if clk_i'event and clk_i = '1' then + -- edge detection pipeline + reg_A <= data_i; + reg_B <= reg_A; + -- new data strobe pipeline delay + strb_reg <= strb_next; + end if; + -- output data pipeline + if clk_i'event and clk_i = '1' then + if dat_strb = '1' then + reg_out <= reg_B; + end if; + end if; + end process pipeline_proc; + -- edge detector + edge_detector_proc: dat_diff <= '1' when reg_A /= reg_B else '0'; + -- lookahead new data strobe + next_strobe_proc: strb_next <= '1' when ((reg_out /= reg_B) and dat_strb = '1') else '0'; + + --============================================================================================= + -- OUTPUT LOGIC + --============================================================================================= + -- connect output ports + data_o_proc: data_o <= reg_out; + strb_o_proc: strb_o <= strb_reg; +end rtl; + diff --git a/src/keyboard_apple.vhd b/src/keyboard_apple.vhd new file mode 100644 index 0000000..f4fc545 --- /dev/null +++ b/src/keyboard_apple.vhd @@ -0,0 +1,502 @@ +------------------------------------------------------------------------------- +-- +-- PS/2 Keyboard interface for the Apple ][ +-- +-- Stephen A. Edwards, sedwards@cs.columbia.edu +-- After an original by Alex Freed +-- i18n & French keyboard by Michel Stempin +-- +------------------------------------------------------------------------------- +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity keyboard_apple is + + generic ( + --KEYMAP : string := "EN-us" -- English US keymap + --KEYMAP : string := "FR-fr" -- French keymap + KEYMAP : string := "DE-de" -- German keymap + ); + + port ( + PS2_Clk : inout std_logic; -- From PS/2 port + PS2_Data : inout std_logic; -- From PS/2 port + CLK_14M : in std_logic; + read : in std_logic; -- Read strobe + reset : in std_logic; + image_out : out unsigned(9 downto 0); -- image number + debug_info_on: out std_logic; -- switch debug on/off + out_color_bw : out std_logic; -- switch color bw + K : out unsigned(7 downto 0) -- Latched, decoded keyboard data + ); +end keyboard_apple; + +architecture rtl of keyboard_apple is + + signal code, latched_code : unsigned(7 downto 0); + signal ScanCode : std_logic_vector(9 downto 0); + signal code_available : std_logic; + signal ascii : unsigned(7 downto 0); -- decoded + signal shifted_code : unsigned(11 downto 0); + + signal key_pressed : std_logic; -- Key pressed & not read + signal ctrl, shift, alt : std_logic; + + signal image : unsigned(9 downto 0); -- disk image number + signal debug_info : std_logic := '0'; -- debug info on off + signal color_bw : std_logic := '0'; -- switch color / bw mode + + -- Special PS/2 keyboard codes + constant KEY_UP_CODE : unsigned(7 downto 0) := X"F0"; + constant EXTENDED_CODE : unsigned(7 downto 0) := X"E0"; + constant LEFT_SHIFT : unsigned(7 downto 0) := X"12"; + constant RIGHT_SHIFT : unsigned(7 downto 0) := X"59"; + constant LEFT_CTRL : unsigned(7 downto 0) := X"14"; + constant ALT_GR : unsigned(7 downto 0) := X"11"; + + +begin + + + inst_ps2 : entity work.Keyboard + port map ( + + Clock => CLK_14M, + Reset => reset, + PS2Clock => PS2_Clk, + PS2Data => PS2_Data, + CodeReady => code_available, + ScanCode => ScanCode); + + K <= key_pressed & "00" & ascii(4 downto 0) when ctrl = '1' else + key_pressed & ascii(6 downto 0); + + code <= unsigned(ScanCode( 7 downto 0)); + + shift_ctrl : process (CLK_14M, reset) + begin + + if rising_edge(CLK_14M) then + + if reset = '1' then + shift <= '0'; + ctrl <= '0'; + + else + + if code_available = '1' then + if ScanCode(8) = '0' then -- make + if code = LEFT_SHIFT or code = RIGHT_SHIFT then + shift <= '1'; + elsif code = LEFT_CTRL then + ctrl <= '1'; + elsif code = ALT_GR then + alt <= '1'; + end if; + elsif ScanCode(8) = '1' then -- break + if code = LEFT_SHIFT or code = RIGHT_SHIFT then + shift <= '0'; + elsif code = LEFT_CTRL then + ctrl <= '0'; + elsif code = ALT_GR then + alt <= '0'; + end if; + end if; + end if; + end if; + end if; + end process shift_ctrl; + + fsm : process (CLK_14M, reset) + begin + + if rising_edge(CLK_14M) then + if reset = '1' then + latched_code <= (others => '0'); + key_pressed <= '0'; + end if; + if read = '1' then + key_pressed <= '0'; + end if; + + if code_available = '1' and ScanCode(8) = '1' then + + if code = X"06" then -- F2 + image <= image +1; + + elsif code = X"04" then -- F3 + image <= image - 1; + + elsif code = X"07" then -- F12 + debug_info <= not debug_info; + + elsif code = X"0B" then -- F6 + color_bw <= not color_bw; + else + -- normal key + latched_code <= code ; + key_pressed <= '1'; + end if; + end if; + end if; + end process fsm; + + image_out <= image; + debug_info_on <= debug_info; + out_color_bw <= color_bw; + + -- PS/2 scancode to ASCII translation + + shifted_code <= "00" & alt & shift & latched_code; + + DE_de: if KEYMAP = "DE-de" generate + with shifted_code select + ascii <= + X"08" when X"066", -- Backspace ("backspace" key) + X"08" when X"166", -- Backspace ("backspace" key) + X"09" when X"00d", -- Horizontal Tab + X"09" when X"10d", -- Horizontal Tab + X"0d" when X"05a", -- Carriage return ("enter" key) + X"0d" when X"15a", -- Carriage return ("enter" key) + X"1b" when X"076", -- Escape ("esc" key) + X"1b" when X"176", -- Escape ("esc" key) + X"20" when X"029", -- Space + X"20" when X"129", -- Space + X"21" when X"116", -- ! + X"22" when X"11e", -- " + X"23" when X"126", -- # + X"24" when X"125", -- $ + X"25" when X"12e", -- + X"26" when X"136", -- & + X"27" when X"052", -- + X"28" when X"13e", -- ( + X"29" when X"146", -- ) + X"2a" when X"15B", -- * + X"2b" when X"05B", -- + + X"2c" when X"041", -- , + X"2d" when X"04a", -- - + X"2e" when X"049", -- . + X"2f" when X"13d", -- / + X"30" when X"045", -- 0 + X"31" when X"016", -- 1 + X"32" when X"01e", -- 2 + X"33" when X"026", -- 3 + X"34" when X"025", -- 4 + X"35" when X"02e", -- 5 + X"36" when X"036", -- 6 + X"37" when X"03d", -- 7 + X"38" when X"03e", -- 8 + X"39" when X"046", -- 9 + X"3a" when X"14c", -- : + X"3b" when X"141", -- ; + X"3c" when X"0C2", -- < + X"3d" when X"145", -- = + X"3e" when X"1C2", -- > + X"3f" when X"14a", -- ? + --X"40" when X"11e", -- @ + X"41" when X"11c", -- A + X"42" when X"132", -- B + X"43" when X"121", -- C + X"44" when X"123", -- D + X"45" when X"124", -- E + X"46" when X"12b", -- F + X"47" when X"134", -- G + X"48" when X"133", -- H + X"49" when X"143", -- I + X"4a" when X"13b", -- J + X"4b" when X"142", -- K + X"4c" when X"14b", -- L + X"4d" when X"13a", -- M + X"4e" when X"131", -- N + X"4f" when X"144", -- O + X"50" when X"14d", -- P + X"51" when X"115", -- Q + X"52" when X"12d", -- R + X"53" when X"11b", -- S + X"54" when X"12c", -- T + X"55" when X"13c", -- U + X"56" when X"12a", -- V + X"57" when X"11d", -- W + X"58" when X"122", -- X + X"59" when X"11a", -- Y + X"5a" when X"135", -- Z + X"5b" when X"2be", -- [ + X"5c" when X"05d", -- \ + X"5d" when X"246", -- ] + --X"5e" when X"136", -- ^ + X"5f" when X"14e", -- _ + X"60" when X"00e", -- ` + X"41" when X"01c", -- A + X"42" when X"032", -- B + X"43" when X"021", -- C + X"44" when X"023", -- D + X"45" when X"024", -- E + X"46" when X"02b", -- F + X"47" when X"034", -- G + X"48" when X"033", -- H + X"49" when X"043", -- I + X"4a" when X"03b", -- J + X"4b" when X"042", -- K + X"4c" when X"04b", -- L + X"4d" when X"03a", -- M + X"4e" when X"031", -- N + X"4f" when X"044", -- O + X"50" when X"04d", -- P + X"51" when X"015", -- Q + X"52" when X"02d", -- R + X"53" when X"01b", -- S + X"54" when X"02c", -- T + X"55" when X"03c", -- U + X"56" when X"02a", -- V + X"57" when X"01d", -- W + X"58" when X"022", -- X + X"59" when X"01a", -- Y + X"5a" when X"035", -- Z + X"7b" when X"23d", -- { + X"7c" when X"15d", -- | + X"7d" when X"254", -- } + X"7e" when X"10e", -- ~ + X"7f" when X"071", -- (Delete OR DEL on numeric keypad) + X"15" when X"074", -- right arrow (cntrl U) + X"08" when X"06b", -- left arrow (BS) + X"0B" when X"075", -- (up arrow) + X"0A" when X"072", -- (down arrow, ^J, LF) + X"7f" when X"171", -- (Delete OR DEL on numeric keypad) + X"00" when others; + end generate DE_de; + + + EN_us: if KEYMAP = "EN-us" generate + with shifted_code select + ascii <= + X"08" when X"066", -- Backspace ("backspace" key) + X"08" when X"166", -- Backspace ("backspace" key) + X"09" when X"00d", -- Horizontal Tab + X"09" when X"10d", -- Horizontal Tab + X"0d" when X"05a", -- Carriage return ("enter" key) + X"0d" when X"15a", -- Carriage return ("enter" key) + X"1b" when X"076", -- Escape ("esc" key) + X"1b" when X"176", -- Escape ("esc" key) + X"20" when X"029", -- Space + X"20" when X"129", -- Space + X"21" when X"116", -- ! + X"22" when X"152", -- " + X"23" when X"126", -- # + X"24" when X"125", -- $ + X"25" when X"12e", -- + X"26" when X"13d", -- + X"27" when X"052", -- + X"28" when X"146", -- + X"29" when X"145", -- + X"2a" when X"13e", -- * + X"2b" when X"155", -- + + X"2c" when X"041", -- , + X"2d" when X"04e", -- - + X"2e" when X"049", -- . + X"2f" when X"04a", -- / + X"30" when X"045", -- 0 + X"31" when X"016", -- 1 + X"32" when X"01e", -- 2 + X"33" when X"026", -- 3 + X"34" when X"025", -- 4 + X"35" when X"02e", -- 5 + X"36" when X"036", -- 6 + X"37" when X"03d", -- 7 + X"38" when X"03e", -- 8 + X"39" when X"046", -- 9 + X"3a" when X"14c", -- : + X"3b" when X"04c", -- ; + X"3c" when X"141", -- < + X"3d" when X"055", -- = + X"3e" when X"149", -- > + X"3f" when X"14a", -- ? + X"40" when X"11e", -- @ + X"41" when X"11c", -- A + X"42" when X"132", -- B + X"43" when X"121", -- C + X"44" when X"123", -- D + X"45" when X"124", -- E + X"46" when X"12b", -- F + X"47" when X"134", -- G + X"48" when X"133", -- H + X"49" when X"143", -- I + X"4a" when X"13b", -- J + X"4b" when X"142", -- K + X"4c" when X"14b", -- L + X"4d" when X"13a", -- M + X"4e" when X"131", -- N + X"4f" when X"144", -- O + X"50" when X"14d", -- P + X"51" when X"115", -- Q + X"52" when X"12d", -- R + X"53" when X"11b", -- S + X"54" when X"12c", -- T + X"55" when X"13c", -- U + X"56" when X"12a", -- V + X"57" when X"11d", -- W + X"58" when X"122", -- X + X"59" when X"135", -- Y + X"5a" when X"11a", -- Z + X"5b" when X"054", -- [ + X"5c" when X"05d", -- \ + X"5d" when X"05b", -- ] + X"5e" when X"136", -- ^ + X"5f" when X"14e", -- _ + X"60" when X"00e", -- ` + X"41" when X"01c", -- A + X"42" when X"032", -- B + X"43" when X"021", -- C + X"44" when X"023", -- D + X"45" when X"024", -- E + X"46" when X"02b", -- F + X"47" when X"034", -- G + X"48" when X"033", -- H + X"49" when X"043", -- I + X"4a" when X"03b", -- J + X"4b" when X"042", -- K + X"4c" when X"04b", -- L + X"4d" when X"03a", -- M + X"4e" when X"031", -- N + X"4f" when X"044", -- O + X"50" when X"04d", -- P + X"51" when X"015", -- Q + X"52" when X"02d", -- R + X"53" when X"01b", -- S + X"54" when X"02c", -- T + X"55" when X"03c", -- U + X"56" when X"02a", -- V + X"57" when X"01d", -- W + X"58" when X"022", -- X + X"59" when X"035", -- Y + X"5a" when X"01a", -- Z + X"7b" when X"154", -- { + X"7c" when X"15d", -- | + X"7d" when X"15b", -- } + X"7e" when X"10e", -- ~ + X"7f" when X"071", -- (Delete OR DEL on numeric keypad) + X"15" when X"074", -- right arrow (cntrl U) + X"08" when X"06b", -- left arrow (BS) + X"0B" when X"075", -- (up arrow) + X"0A" when X"072", -- (down arrow, ^J, LF) + X"7f" when X"171", -- (Delete OR DEL on numeric keypad) + X"00" when others; + end generate EN_us; + + FR_fr: if KEYMAP = "FR-fr" generate + with shifted_code select + ascii <= + X"08" when X"066", -- Backspace ("backspace" key) + X"08" when X"166", -- Backspace ("backspace" key) + X"09" when X"00d", -- Horizontal Tab + X"09" when X"10d", -- Horizontal Tab + X"0d" when X"05a", -- Carriage return ("enter" key) + X"0d" when X"15a", -- Carriage return ("enter" key) + X"1b" when X"076", -- Escape ("esc" key) + X"1b" when X"176", -- Escape ("esc" key) + X"20" when X"029", -- Space + X"20" when X"129", -- Space + X"21" when X"04a", -- ! + X"22" when X"026", -- " + X"23" when X"226", -- # + X"24" when X"05b", -- $ + X"25" when X"152", -- % + X"26" when X"016", -- & + X"27" when X"025", -- ' + X"28" when X"02e", -- ( + X"29" when X"04e", -- ) + X"2a" when X"05d", -- * + X"2b" when X"155", -- + + X"2c" when X"03a", -- , + X"2d" when X"036", -- - + X"2e" when X"141", -- . + X"2f" when X"149", -- / + X"30" when X"145", -- 0 + X"31" when X"116", -- 1 + X"32" when X"11e", -- 2 + X"33" when X"126", -- 3 + X"34" when X"125", -- 4 + X"35" when X"12e", -- 5 + X"36" when X"136", -- 6 + X"37" when X"13d", -- 7 + X"38" when X"13e", -- 8 + X"39" when X"146", -- 9 + X"3a" when X"049", -- : + X"3b" when X"041", -- ; + X"3c" when X"061", -- < + X"3d" when X"055", -- = + X"3e" when X"161", -- > + X"3f" when X"13a", -- ? + X"40" when X"245", -- @ + X"41" when X"115", -- A + X"42" when X"132", -- B + X"43" when X"121", -- C + X"44" when X"123", -- D + X"45" when X"124", -- E + X"46" when X"12b", -- F + X"47" when X"134", -- G + X"48" when X"133", -- H + X"49" when X"143", -- I + X"4a" when X"13b", -- J + X"4b" when X"142", -- K + X"4c" when X"14b", -- L + X"4d" when X"14c", -- M + X"4e" when X"131", -- N + X"4f" when X"144", -- O + X"50" when X"14d", -- P + X"51" when X"11c", -- Q + X"52" when X"12d", -- R + X"53" when X"11b", -- S + X"54" when X"12c", -- T + X"55" when X"13c", -- U + X"56" when X"12a", -- V + X"57" when X"11a", -- W + X"58" when X"122", -- X + X"59" when X"135", -- Y + X"5a" when X"11d", -- Z + X"5b" when X"22e", -- [ + X"5c" when X"23e", -- \ + X"5d" when X"24e", -- ] + X"5e" when X"054", -- ^ + X"5f" when X"03e", -- _ + X"60" when X"23d", -- ` + X"41" when X"015", -- A + X"42" when X"032", -- B + X"43" when X"021", -- C + X"44" when X"023", -- D + X"45" when X"024", -- E + X"46" when X"02b", -- F + X"47" when X"034", -- G + X"48" when X"033", -- H + X"49" when X"043", -- I + X"4a" when X"03b", -- J + X"4b" when X"042", -- K + X"4c" when X"04b", -- L + X"4d" when X"04c", -- M + X"4e" when X"031", -- N + X"4f" when X"044", -- O + X"50" when X"04d", -- P + X"51" when X"01c", -- Q + X"52" when X"02d", -- R + X"53" when X"01b", -- S + X"54" when X"02c", -- T + X"55" when X"03c", -- U + X"56" when X"02a", -- V + X"57" when X"01a", -- W + X"58" when X"022", -- X + X"59" when X"035", -- Y + X"5a" when X"01d", -- Z + X"7b" when X"225", -- { + X"7c" when X"236", -- | + X"7d" when X"255", -- } + X"7e" when X"21e", -- ~ + X"7f" when X"071", -- (Delete OR DEL on numeric keypad) + X"15" when X"074", -- right arrow (cntrl U) + X"08" when X"06b", -- left arrow (BS) + X"0B" when X"075", -- (up arrow) + X"0A" when X"072", -- (down arrow, ^J, LF) + X"7f" when X"171", -- (Delete OR DEL on numeric keypad) + X"00" when others; + end generate FR_fr; + +end rtl; diff --git a/src/papilio_duo.ucf b/src/papilio_duo.ucf new file mode 100644 index 0000000..5c7e16b --- /dev/null +++ b/src/papilio_duo.ucf @@ -0,0 +1,112 @@ +# UCF file for the Papilio DUO board +# Generated by pin_converter, written by Kevin Lindsey +# https://github.com/thelonious/papilio_pins/tree/development/pin_converter +## Prohibit the automatic placement of pins that are connected to VCC or GND for configuration. +CONFIG PROHIBIT=P144; +CONFIG PROHIBIT=P69; +CONFIG PROHIBIT=P60; + +PIN "pll/clkout2_buf.O" CLOCK_DEDICATED_ROUTE = FALSE; + +NET clk LOC="P94" |IOSTANDARD=LVTTL ; # CLK +#NET RX LOC="P46" | IOSTANDARD=LVTTL; # RX +#NET TX LOC="P141" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # TX +#NET ARDUINO_RESET LOC="P139" | IOSTANDARD=LVTTL; # ARDUINO_RESET +#NET RS232_RX LOC="P116" | IOSTANDARD=LVTTL; # A0 +#NET RS232_TX LOC="P117" | IOSTANDARD=LVTTL; # A1 +NET sd_dat LOC="P118" |IOSTANDARD=LVTTL; # A2 +NET sd_cmd LOC="P115" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # B0 +NET sd_clk LOC="P114" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # B1 +NET sd_dat3 LOC="P112" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # B2 + +NET ps2_dat LOC="P120" | IOSTANDARD=LVTTL |PULLUP; # A4 +NET ps2_clk LOC="P121" | IOSTANDARD=LVTTL |PULLUP; # A5 +#NET JOYSTICK1_5 LOC="P123" | IOSTANDARD=LVTTL; # A6 +#NET JOYSTICK1_9 LOC="P124" | IOSTANDARD=LVTTL; # A7 +#NET JOYSTICK1_4 LOC="P126" | IOSTANDARD=LVTTL; # A8 +#NET JOYSTICK1_3 LOC="P127" | IOSTANDARD=LVTTL; # A9 +#NET JOYSTICK1_7 LOC="P131" | IOSTANDARD=LVTTL; # A10 +#NET JOYSTICK1_2 LOC="P132" | IOSTANDARD=LVTTL; # A11 +#NET JOYSTICK1_6 LOC="P133" | IOSTANDARD=LVTTL; # A12 +#NET JOYSTICK1_1 LOC="P134" | IOSTANDARD=LVTTL; # A13 +#NET SD_nCS LOC="P112" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # B2 +#NET SW_LEFT LOC="P111" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # B3 +#NET SW_UP LOC="P105" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # B4 +NET RESET_I LOC="P102" |IOSTANDARD=LVTTL; # B5 +#NET SW_DOWN LOC="P101" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # B6 +#NET SW_RIGHT LOC="P100" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # B7 +NET vga_hs LOC="P99" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # C0 +NET vga_vs LOC="P97" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # C1 +NET vga_b(0) LOC="P93" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # C2 +NET O_AUDIO_L LOC="P88" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # C3 +NET O_AUDIO_R LOC="P85" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # C4 +NET vga_b(1) LOC="P83" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # C5 +NET vga_b(2) LOC="P81" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # C6 +NET vga_b(3) LOC="P79" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # C7 +NET vga_g(0) LOC="P75" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # C8 +NET vga_g(1) LOC="P67" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # C9 +NET vga_g(2) LOC="P62" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # C10 +NET vga_g(3) LOC="P59" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # C11 +NET vga_r(3) LOC="P57" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # C12 +NET vga_r(2) LOC="P55" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # C13 +NET vga_r(1) LOC="P50" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # C14 +NET vga_r(0) LOC="P47" |IOSTANDARD=LVTTL |DRIVE=8 |SLEW=FAST; # C15 +#NET JOYSTICK2_5 LOC="P98" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # D0 +#NET JOYSTICK2_4 LOC="P95" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # D1 +#NET JOYSTICK2_3 LOC="P92" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # D2 +#NET JOYSTICK2_2 LOC="P87" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # D3 +#NET JOYSTICK2_1 LOC="P84" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # D4 +#NET JOYSTICK2_6 LOC="P82" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # D5 +#NET JOYSTICK2_7 LOC="P80" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # D6 +#NET JOYSTICK2_9 LOC="P78" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # D7 +#NET PS2_CLK2 LOC="P74" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # D8 +#NET PS2_DAT2 LOC="P66" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # D9 +#NET AUDIO2_RIGHT LOC="P61" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # D10 +#NET AUDIO2_LEFT LOC="P58" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # D11 +NET LED(0) LOC="P56" | IOSTANDARD=LVTTL | DRIVE=8; # D12 +NET LED(1) LOC="P51" | IOSTANDARD=LVTTL | DRIVE=8; # D13 +NET LED(2) LOC="P48" | IOSTANDARD=LVTTL | DRIVE=8; # D14 +NET LED(3) LOC="P39" | IOSTANDARD=LVTTL | DRIVE=8; # D15 +NET sram_addr(0) LOC="P7" |IOSTANDARD=LVTTL | DRIVE=8 | IOB=TRUE |SLEW=FAST ; # SRAM_ADDR0 +NET sram_addr(1) LOC="P8" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR1 +NET sram_addr(2) LOC="P9" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR2 +NET sram_addr(3) LOC="P10" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR3 +NET sram_addr(4) LOC="P11" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR4 +NET sram_addr(5) LOC="P5" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR5 +NET sram_addr(6) LOC="P2" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR6 +NET sram_addr(7) LOC="P1" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR7 +NET sram_addr(8) LOC="P143" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR8 +NET sram_addr(9) LOC="P142" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR9 +NET sram_addr(10) LOC="P43" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR10 +NET sram_addr(11) LOC="P41" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR11 +NET sram_addr(12) LOC="P40" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR12 +NET sram_addr(13) LOC="P35" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR13 +NET sram_addr(14) LOC="P34" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR14 +NET sram_addr(15) LOC="P27" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR15 +NET sram_addr(16) LOC="P29" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR16 +NET sram_addr(17) LOC="P33" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR17 +NET sram_addr(18) LOC="P32" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR18 +NET sram_addr(19) LOC="P44" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR19 +NET sram_addr(20) LOC="P30" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_ADDR20 +NET sram_dq(0) LOC="P14" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_DATA0 +NET sram_dq(1) LOC="P15" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_DATA1 +NET sram_dq(2) LOC="P16" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_DATA2 +NET sram_dq(3) LOC="P17" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_DATA3 +NET sram_dq(4) LOC="P21" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_DATA4 +NET sram_dq(5) LOC="P22" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_DATA5 +NET sram_dq(6) LOC="P23" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST ; # SRAM_DATA6 +NET sram_dq(7) LOC="P24" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_DATA7 +NET sram_ce_n LOC="P12" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_CE +NET sram_we_n LOC="P6" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_WE +NET sram_oe_n LOC="P26" |IOSTANDARD=LVTTL | DRIVE=8 |IOB=TRUE |SLEW=FAST; # SRAM_OE +#NET JTAG_TMS LOC="P107" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # JTAG_TMS +#NET JTAG_TCK LOC="P109" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # JTAG_TCK +#NET JTAG_TDI LOC="P110" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # JTAG_TDI +#NET JTAG_TDO LOC="P106" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # JTAG_TDO +#NET FLASH_CS LOC="P38" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # FLASH_CS +#NET FLASH_CK LOC="P70" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # FLASH_CK +#NET FLASH_SI LOC="P64" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST; # FLASH_SI +#NET FLASH_SO LOC="P65" | IOSTANDARD=LVTTL | DRIVE=8 | SLEW=FAST | PULLUP; # FLASH_SO + +NET "CLK" TNM_NET = CLK; +TIMESPEC TS_CLK = PERIOD "CLK" 31.25 ns HIGH 50%; diff --git a/src/spi_controller.vhd b/src/spi_controller.vhd new file mode 100644 index 0000000..24a6489 --- /dev/null +++ b/src/spi_controller.vhd @@ -0,0 +1,471 @@ +------------------------------------------------------------------------------- +-- +-- 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; diff --git a/src/timing_generator.vhd b/src/timing_generator.vhd new file mode 100644 index 0000000..c59ab42 --- /dev/null +++ b/src/timing_generator.vhd @@ -0,0 +1,174 @@ +------------------------------------------------------------------------------- +-- +-- Apple ][ Timing logic +-- +-- Stephen A. Edwards, sedwards@cs.columbia.edu +-- +-- Taken more-or-less verbatim from the schematics in the +-- Apple ][ reference manual +-- +-- This takes a 14.31818 MHz master clock and divides it down to generate +-- the various lower-frequency signals (e.g., 7M, phase 0, colorburst) +-- as well as horizontal and vertical blanking and sync signals for the video +-- and the video addresses. +-- +------------------------------------------------------------------------------- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity timing_generator is + + port ( + CLK_14M : in std_logic; -- 14.31818 MHz master clock + CLK_7M_out : out std_logic := '0'; + Q3_out : out std_logic := '0'; -- 2 MHz signal in phase with PHI0 + RAS_N_out : out std_logic := '0'; + CAS_N_out : out std_logic := '0'; + AX_out : out std_logic := '0'; + PHI0_out : out std_logic := '0'; -- 1.0 MHz processor clock + PRE_PHI0_out : out std_logic := '0'; -- One 14M cycle before + COLOR_REF_out : out std_logic := '0'; -- 3.579545 MHz colorburst + + TEXT_MODE : in std_logic; + PAGE2 : in std_logic; + HIRES : in std_logic; + + VIDEO_ADDRESS : out unsigned(15 downto 0); + H0 : out std_logic; + VA : out std_logic; -- Character row address + VB : out std_logic; + VC : out std_logic; + V2 : out std_logic; + V4 : out std_logic; + HBL_out : out std_logic; -- Horizontal blanking + VBL_out : out std_logic; -- Vertical blanking + BLANK : out std_logic; -- Composite blanking + LDPS_N : out std_logic; + LD194 : out std_logic + ); + +end timing_generator; + +architecture rtl of timing_generator is + + signal H : unsigned(6 downto 0) := "0000000"; + signal V : unsigned(8 downto 0) := "011111010"; + signal COLOR_DELAY_N : std_logic; + + signal CLK_7M : std_logic := '0'; + signal Q3 : std_logic := '0'; -- 2 MHz signal in phase with PHI0 + signal RAS_N : std_logic := '0'; + signal CAS_N : std_logic := '0'; + signal AX : std_logic := '0'; + signal PHI0 : std_logic := '0'; -- 1.0 MHz processor clock + signal PRE_PHI0 : std_logic := '0'; -- One 14M cycle before + signal COLOR_REF : std_logic := '0'; -- 3.579545 MHz colorburst + + signal HBL : std_logic; -- Horizontal blanking + signal VBL : std_logic; -- Vertical blanking + +begin + + -- To generate the once-a-line hiccup: D1 pin 6 + COLOR_DELAY_N <= + not (not COLOR_REF and (not AX and not CAS_N) and PHI0 and not H(6)); + + + CLK_7M_out <= CLK_7M; + Q3_out <= Q3; + RAS_N_out <= RAS_N; + CAS_N_out <= CAS_N; + AX_out <= AX; + PHI0_out <= PHI0; -- 1.0 MHz processor clock + PRE_PHI0_out <= PRE_PHI0; -- One 14M cycle before + COLOR_REF_out <= COLOR_REF; -- 3.579545 MHz colorburst + HBL_out <= HBL; -- Horizontal blanking + VBL_out <= VBL; -- Vertical blanking + + -- The DRAM signal generator + C2_74S195: process (CLK_14M) + begin + if rising_edge(CLK_14M) then + if Q3 = '1' then -- shift + (Q3, CAS_N, AX, RAS_N) <= + unsigned'(CAS_N, AX, RAS_N, '0'); + else -- load + (Q3, CAS_N, AX, RAS_N) <= + unsigned'(RAS_N, AX, COLOR_DELAY_N, AX); + end if; + end if; + end process; + + -- The main clock signal generator + B1_74S175 : process (CLK_14M) + begin + if rising_edge(CLK_14M) then + COLOR_REF <= CLK_7M xor COLOR_REF; + CLK_7M <= not CLK_7M; + PHI0 <= PRE_PHI0; + if AX = '1' then + PRE_PHI0 <= not (Q3 xor PHI0); -- B1 pin 10 + end if; + end if; + end process; + + LDPS_N <= not (PHI0 and not AX and not CAS_N); + LD194 <= not (PHI0 and not AX and not CAS_N and not CLK_7M); + + -- Four four-bit presettable binary counters + -- Seven-bit horizontal counter counts 0, 40, 41, ..., 7F (65 states) + -- Nine-bit vertical counter counts $FA .. $1FF (262 states) + D11D12D13D14_74LS161 : process (CLK_14M) + begin + if rising_edge(CLK_14M) then + -- True the cycle before the rising edge of LDPS_N: emulates + -- the effects of using LDPS_N as the clock for the video counters + if (PHI0 and not AX and ((Q3 and RAS_N) or + (not Q3 and COLOR_DELAY_N))) = '1' then + if H(6) = '0' then H <= "1000000"; + else + H <= H + 1; + if H = "1111111" then + V <= V + 1; + if V = "111111111" then V <= "011111010"; end if; + end if; + end if; + end if; + end if; + + end process; + + H0 <= H(0); + VA <= V(0); + VB <= V(1); + VC <= V(2); + V2 <= V(5); + V4 <= V(7); + + HBL <= not (H(5) or (H(3) and H(4))); + VBL <= V(6) and V(7); + + BLANK <= HBL or VBL; + + -- V_SYNC <= VBL and V(5) and not V(4) and not V(3) and + -- not V(2) and (H(4) or H(3) or H(5)); + -- H_SYNC <= HBL and H(3) and not H(2); + + -- SYNC <= not (V_SYNC or H_SYNC); + -- COLOR_BURST <= HBL and H(2) and H(3) and (COLOR_REF or TEXT_MODE); + + -- Video address calculation + VIDEO_ADDRESS(2 downto 0) <= H(2 downto 0); + VIDEO_ADDRESS(6 downto 3) <= (not H(5) & V(6) & H(4) & H(3)) + + ( V(7) & not H(5) & V(7) & '1') + + ( "000" & V(6)); + VIDEO_ADDRESS(9 downto 7) <= V(5 downto 3); + VIDEO_ADDRESS(14 downto 10) <= + ( "00" & HBL & PAGE2 & not PAGE2) when HIRES = '0' else + (PAGE2 & not PAGE2 & V(2 downto 0)); + + VIDEO_ADDRESS(15) <= '0'; + +end rtl; diff --git a/src/timing_testbench.vhd b/src/timing_testbench.vhd new file mode 100644 index 0000000..ab249c1 --- /dev/null +++ b/src/timing_testbench.vhd @@ -0,0 +1,25 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity timing_testbench is + +end timing_testbench; + +architecture behavioral of timing_testbench is + + signal CLK_14M : std_logic := '0'; + +begin + + uut : entity work.timing_generator + port map ( + CLK_14M => CLK_14M, + TEXT_MODE => '1', + PAGE2 => '0', + HIRES => '1' + ); + + CLK_14M <= not CLK_14M after 34.920639355 ns; + +end behavioral; diff --git a/src/vga_controller.vhd b/src/vga_controller.vhd new file mode 100644 index 0000000..1de2bce --- /dev/null +++ b/src/vga_controller.vhd @@ -0,0 +1,326 @@ +------------------------------------------------------------------------------- +-- +-- A VGA line-doubler for an Apple ][ +-- +-- Stephen A. Edwards, sedwards@cs.columbia.edu +-- +-- +-- FIXME: This is all wrong +-- +-- The Apple ][ uses a 14.31818 MHz master clock. It outputs a new +-- horizontal line every 65 * 14 + 2 = 912 14M cycles. The extra two +-- are from the "extended cycle" used to keep the 3.579545 MHz +-- colorburst signal in sync. Of these, 40 * 14 = 560 are active video. +-- +-- In graphics mode, the Apple effectively generates 140 four-bit pixels +-- output serially (i.e., with 3.579545 MHz pixel clock). In text mode, +-- it generates 280 one-bit pixels (i.e., with a 7.15909 MHz pixel clock). +-- +-- We capture 140 four-bit nibbles for each line and interpret them in +-- one of the two modes. In graphics mode, each is displayed as a +-- single pixel of one of 16 colors. In text mode, each is displayed +-- as two black or white pixels. +-- +-- The VGA display is nominally 640 X 480, but we use a 14.31818 MHz +-- dot clock. To stay in sync with the Apple, we generate a new line +-- every 912 / 2 = 456 14M cycles= 31.8 us, a 31.4 kHz horizontal +-- refresh rate. Of these, 280 will be active video. +-- +-- One set of suggested VGA timings: +-- +-- ______________________ ________ +-- ________| VIDEO |________| VIDEO +-- |-C-|----------D-----------|-E-| +-- __ ______________________________ ___________ +-- |_| |_| +-- |B| +-- |---------------A----------------| +-- +-- A = 31.77 us Scanline time +-- B = 3.77 us Horizontal sync time +-- C = 1.89 us Back porch +-- D = 25.17 us Active video +-- E = 0.94 us Front porch +-- +-- We use A = 456 / 14.31818 MHz = 31.84 us +-- B = 54 / 14.31818 MHz = 3.77 us +-- C = 106 / 14.31818 MHz = 7.40 us +-- D = 280 / 14.31818 MHz = 19.56 us +-- E = 16 / 14.31818 MHz = 1.12 us +------------------------------------------------------------------------------- +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity vga_controller is + + port ( + CLK_28M : in std_logic; -- 14.31818 MHz master clock + + VIDEO : in std_logic; -- from the Apple video generator + COLOR_LINE : in std_logic; + HBL : in std_logic; + VBL : in std_logic; + LD194 : in std_logic; + + --VGA_CLK : out std_logic; + VGA_HS : out std_logic; -- Active low + VGA_VS : out std_logic; -- Active low + --VGA_BLANK : out std_logic; + VGA_R : out unsigned(9 downto 0); + VGA_G : out unsigned(9 downto 0); + VGA_B : out unsigned(9 downto 0) + ); + +end vga_controller; + +architecture rtl of vga_controller is + + -- Double-ported RAM (one read port, one write port) + -- that holds two lines of 560 pixels + type line_memory_t is array (0 to 2047) of std_logic; + signal line_memory : line_memory_t; + + -- RGB values from Linards Ticmanis, + -- http://newsgroups.derkeiler.com/Archive/Comp/comp.sys.apple2/2005-09/msg00534.html + + type basis_color is array(0 to 3) of unsigned(7 downto 0); + constant basis_r : basis_color := ( X"88", X"38", X"07", X"38" ); + constant basis_g : basis_color := ( X"22", X"24", X"67", X"52" ); + constant basis_b : basis_color := ( X"2C", X"A0", X"2C", X"07" ); + + signal ram_write_addr : unsigned(10 downto 0); + signal ram_we : std_logic; + signal ram_read_addr : unsigned(10 downto 0); + signal ram_data_out : std_logic; + + signal shift_reg : unsigned(5 downto 0); -- Last six pixels + + signal last_hbl : std_logic; + signal hcount : unsigned(10 downto 0); + signal hcount2 : unsigned(10 downto 0); + signal vcount : unsigned(5 downto 0); + signal even_line : std_logic; + signal hactive, hactive_early2, hactive_early1 : std_logic; + + constant VGA_SCANLINE : integer := 456*2; -- Must be 456*2 (set by the Apple) + + constant VGA_HSYNC : integer := 54 * 2; + constant VGA_BACK_PORCH : integer := 66 * 2; + constant VGA_ACTIVE : integer := 282 * 2; + constant VGA_FRONT_PORCH : integer := 54 * 2; + + -- VGA_HSYNC + VGA_BACK_PORCH + VGA_ACTIVE + VGA_FRONT_PORCH = VGA_SCANLINE + + constant VBL_TO_VSYNC : integer := 33; + constant VGA_VSYNC_LINES : integer := 3; + + signal VGA_VS_I, VGA_HS_I : std_logic; + + signal video_active : std_logic; + signal vbl_delayed, vbl_delayed2 : std_logic; + signal hbl_delayed : std_logic; + signal color_line_delayed_1, color_line_delayed_2 : std_logic; + + signal VGA_R_mono : unsigned(9 downto 0); + signal VGA_G_mono : unsigned(9 downto 0); + signal VGA_B_mono : unsigned(9 downto 0); + + signal VGA_R_col : unsigned(9 downto 0); + signal VGA_G_col : unsigned(9 downto 0); + signal VGA_B_col : unsigned(9 downto 0); + + signal disp_color : std_logic := '0'; + + +begin + + VGA_R <= VGA_R_mono when (COLOR_LINE = '0') else VGA_R_col; + VGA_G <= VGA_G_mono when (COLOR_LINE = '0') else VGA_G_col; + VGA_B <= VGA_B_mono when (COLOR_LINE = '0') else VGA_B_col; + + delay_hbl : process (CLK_28M) + begin + if rising_edge(CLK_28M) then + if LD194 = '0' then + hbl_delayed <= HBL; + end if; + end if; + end process; + + hcount_vcount_control : process (CLK_28M) + begin + if rising_edge(CLK_28M) then + if last_hbl = '1' and hbl_delayed = '0' then -- Falling edge + color_line_delayed_2 <= color_line_delayed_1; + color_line_delayed_1 <= COLOR_LINE; + hcount <= (others => '0'); + vbl_delayed2 <= vbl_delayed; + vbl_delayed <= VBL; + if vbl_delayed = '1' then + even_line <= '0'; + vcount <= vcount + 1; + else + vcount <= (others => '0'); + even_line <= not even_line; + end if; + else + hcount <= hcount + 1; + end if; + last_hbl <= hbl_delayed; + end if; + end process hcount_vcount_control; + + hsync_gen : process (CLK_28M) + begin + if rising_edge(CLK_28M) then + if hcount = VGA_ACTIVE + VGA_FRONT_PORCH or + hcount = VGA_SCANLINE + VGA_ACTIVE + VGA_FRONT_PORCH then + VGA_HS_I <= '0'; + elsif hcount = VGA_ACTIVE + VGA_FRONT_PORCH + VGA_HSYNC or + hcount = VGA_SCANLINE + VGA_ACTIVE + VGA_FRONT_PORCH + VGA_HSYNC then + VGA_HS_I <= '1'; + end if; + + hactive <= hactive_early1; + hactive_early1 <= hactive_early2; + + if hcount = VGA_SCANLINE - 1 or + hcount = VGA_SCANLINE + VGA_SCANLINE - 1 then + hactive_early2 <= '1'; + elsif hcount = VGA_ACTIVE or + hcount = VGA_ACTIVE + VGA_SCANLINE then + hactive_early2 <= '0'; + end if; + end if; + end process hsync_gen; + + VGA_HS <= VGA_HS_I; + + vsync_gen : process (CLK_28M) + begin + if rising_edge(CLK_28M) then + if vcount = VBL_TO_VSYNC then + VGA_VS_I <= '0'; + elsif vcount = VBL_TO_VSYNC + VGA_VSYNC_LINES then + VGA_VS_I <= '1'; + end if; + end if; + end process vsync_gen; + + VGA_VS <= VGA_VS_I; + + hcount2 <= hcount - VGA_SCANLINE; + + ram_read_addr <= + even_line & hcount(9 downto 0) when hcount < VGA_SCANLINE else + even_line & hcount2(9 downto 0); + + shifter: process (CLK_28M) + begin + if rising_edge(CLK_28M) then + shift_reg <= ram_data_out & shift_reg(5 downto 1); + + if shift_reg(0) = shift_reg(4) and shift_reg(5) = shift_reg(1) then + disp_color <= '1'; + else + disp_color <= '0'; + end if; + end if; + end process; + + ram_write_addr <= (not even_line) & hcount(10 downto 1); + ram_we <= '1' when hcount(0) = '1' else '0'; + + video_active <= hactive and not vbl_delayed2; + + pixel_generator: process (CLK_28M) + variable r, g, b : unsigned(7 downto 0); + begin + if rising_edge(CLK_28M) then + r := X"00"; + g := X"00"; + b := X"00"; + if video_active = '1' then + --if color_line_delayed_2 = '0' then -- Monochrome mode + if shift_reg(2) = '1' then + r := X"FF"; g := X"FF"; b := X"FF"; + end if; + + --end if; + + VGA_R_mono <= r & r(7 downto 6); + VGA_G_mono <= g & g(7 downto 6); + VGA_B_mono <= b & b(7 downto 6); + + end if; + end if; + end process pixel_generator; + +pixel_generator_color: process (CLK_28M) + variable r, g, b : unsigned(7 downto 0); + begin + if rising_edge(CLK_28M) then + r := X"00"; + g := X"00"; + b := X"00"; + if video_active = '1' then + + --if disp_color = '1' then + + -- Tint of adjacent pixels is consistent : display the color + + if shift_reg(1) = '1' then + r := r + basis_r(to_integer(hcount + 1)); + g := g + basis_g(to_integer(hcount + 1)); + b := b + basis_b(to_integer(hcount + 1)); + end if; + if shift_reg(2) = '1' then + r := r + basis_r(to_integer(hcount + 2)); + g := g + basis_g(to_integer(hcount + 2)); + b := b + basis_b(to_integer(hcount + 2)); + end if; + if shift_reg(3) = '1' then + r := r + basis_r(to_integer(hcount + 3)); + g := g + basis_g(to_integer(hcount + 3)); + b := b + basis_b(to_integer(hcount + 3)); + end if; + if shift_reg(4) = '1' then + r := r + basis_r(to_integer(hcount)); + g := g + basis_g(to_integer(hcount)); + b := b + basis_b(to_integer(hcount)); + end if; +-- else +-- + -- Tint is changing: display only black, gray, or white + +-- case shift_reg(3 downto 2) is +-- when "11" => r := X"FF"; g := X"FF"; b := X"FF"; +-- when "01" => r := X"80"; g := X"80"; b := X"80"; +-- when "10" => r := X"80"; g := X"80"; b := X"80"; +-- when others => r := X"00"; g := X"00"; b := X"00"; +-- end case; + --end if; + + end if; + + VGA_R_col <= r & r(7 downto 6); + VGA_G_col <= g & g(7 downto 6); + VGA_B_col <= b & b(7 downto 6); + + end if; + end process pixel_generator_color; + + -- The two-port RAM that stores the line data + line_storage : process (CLK_28M) + begin + if rising_edge(CLK_28M) then + if ram_we = '1' then + line_memory(to_integer(ram_write_addr)) <= VIDEO; + end if; + ram_data_out <= line_memory(to_integer(ram_read_addr)); + end if; + end process line_storage; + + +end rtl; diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..483c81d --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,3 @@ + +add_executable(dsk2nib dsk2nib.c) +install(TARGETS dsk2nib DESTINATION dsk2nib) diff --git a/tools/dsk2nib.c b/tools/dsk2nib.c new file mode 100644 index 0000000..402a5a1 --- /dev/null +++ b/tools/dsk2nib.c @@ -0,0 +1,228 @@ +/*********************************************************************** + * + * Apple ][ .dsk file to .nib file format converter + * + * Stephen A. Edwards, sedwards@cs.columbia.edu + * + * Adapted from the "dsk2pdb" program supplied with the PalmApple/Appalm ][ + * + *********************************************************************** + */ + +#include +#include +#include + +typedef unsigned char BYTE; + +#define VOLUME_NUMBER 254 + +#define TRACKS 35 +#define SECTORS 16 +#define SECTOR_SIZE 256 +#define DOS_TRACK_BYTES (SECTORS * SECTOR_SIZE) + +#define RAW_TRACK_BYTES 0x1A00 + + +FILE *disk_file; +BYTE dos_track[SECTORS * SECTOR_SIZE]; + +BYTE raw_track[RAW_TRACK_BYTES]; +BYTE *target; /* Where to write in the raw_track buffer */ + +#define write_byte(x) (*target++ = (x)) + +BYTE GCR_encoding_table[64] = { + 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6, + 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3, + 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC, + 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3, + 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, + 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC, + 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, + 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF }; + +int Swap_Bit[4] = { 0, 2, 1, 3 }; /* swap lower 2 bits */ +BYTE GCR_buffer[256]; +BYTE GCR_buffer2[86]; + +/* physical sector no. to DOS 3.3 logical sector no. table */ +int Logical_Sector[16] = { + 0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, + 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF }; + +/* + * write an FM encoded value, used in writing address fields + */ +void FM_encode( BYTE data ) +{ + write_byte( (data >> 1) | 0xAA ); + write_byte( data | 0xAA ); +} + +/* + * Write 0xFF sync bytes + */ +void write_sync( int length ) +{ + while( length-- ) write_byte( 0xFF ); +} + +void write_address_field( int volume, int track, int sector ) +{ + /* + * write address mark + */ + write_byte( 0xD5 ); + write_byte( 0xAA ); + write_byte( 0x96 ); + + /* + * write Volume, Track, Sector & Check-sum + */ + FM_encode( volume ); + FM_encode( track ); + FM_encode( sector ); + FM_encode( volume ^ track ^ sector ); + + /* + * write epilogue + */ + write_byte( 0xDE ); + write_byte( 0xAA ); + write_byte( 0xEB ); +} + +/* + * 6-and-2 group encoding: the heart of the "nibblization" procedure + */ +void encode62( BYTE *page ) +{ + int i, j; + + /* 86 * 3 = 258, so the first two byte are encoded twice */ + GCR_buffer2[0] = Swap_Bit[page[1] & 0x03]; + GCR_buffer2[1] = Swap_Bit[page[0] & 0x03]; + + /* save higher 6 bits in GCR_buffer and lower 2 bits in GCR_buffer2 */ + for( i = 255, j = 2; i >= 0; i--, j = j == 85? 0: j + 1 ) { + GCR_buffer2[j] = (GCR_buffer2[j] << 2) | Swap_Bit[page[i] & 0x03]; + GCR_buffer[i] = page[i] >> 2; + } + + /* clear off higher 2 bits of GCR_buffer2 set in the last call */ + for( i = 0; i < 86; i++ ) + GCR_buffer2[i] &= 0x3f; +} + +void write_data_field(BYTE *page) +{ + int i; + BYTE last, checksum; + + encode62(page); + + /* write prologue */ + write_byte( 0xD5 ); + write_byte( 0xAA ); + write_byte( 0xAD ); + + /* write GCR encoded data */ + for ( i = 0x55, last = 0 ; i >= 0 ; --i ) { + checksum = last ^ GCR_buffer2[i]; + write_byte( GCR_encoding_table[checksum] ); + last = GCR_buffer2[i]; + } + for ( i = 0 ; i < 256 ; ++i ) { + checksum = last ^ GCR_buffer[i]; + write_byte( GCR_encoding_table[checksum] ); + last = GCR_buffer[i]; + } + + /* write checksum and epilogue */ + write_byte( GCR_encoding_table[last] ); + write_byte( 0xDE ); + write_byte( 0xAA ); + write_byte( 0xEB ); +} + +int main(int argc, char **argv) +{ + char nibname[256], *p; + FILE *nib_file; + int track; + + if (argc < 2) { + fprintf(stderr, "Usage: %s [NIB file]\n", argv[0]); + exit(1); + } + + if (!(disk_file = fopen(argv[1], "rb"))) { + fprintf(stderr, "Unable to mount disk file \"%s\"\n", argv[1]); + exit(1); + } + + if (argc > 2) { + strcpy(nibname, argv[2]); + } else { + /* Strip leading pathname from DSK name */ + for (p = argv[1]; *p; p++) { + if (*p == '/' || *p == '\\') + argv[1] = p + 1; + } + strcpy(nibname, argv[1]); + /* Strip trailing .dsk, if any, from DSK name */ + p = nibname + strlen(nibname); + if (p[-4] == '.' && + (p[-3] == 'd' || p[-3] == 'D') && + (p[-2] == 's' || p[-2] == 'S') && + (p[-1] == 'k' || p[-1] == 'K')) p[-4] = 0; + strcat(nibname, ".nib"); + } + + if (!(nib_file = fopen(nibname, "wb"))) { + fprintf(stderr, "Unable to write \"%s\"\n", nibname); + exit(1); + } + + /* Read, convert, and write each track */ + + for (track = 0 ; track < TRACKS ; ++track ) { + int sector; + + fseek( disk_file, track * DOS_TRACK_BYTES, 0L ); + if ( fread(dos_track, 1, DOS_TRACK_BYTES, disk_file) != DOS_TRACK_BYTES ) { + fprintf(stderr, "Unexpected end of disk data\n"); + exit(1); + } + + target = raw_track; + + for ( sector = 0 ; sector < SECTORS ; sector ++ ) { + write_sync( 38 ); /* Inter-sector gap */ + write_address_field( VOLUME_NUMBER, track, sector ); + write_sync( 8 ); + write_data_field( dos_track + Logical_Sector[sector] * SECTOR_SIZE ); + } + + /* Pad rest of buffer with sync bytes */ + + while (target != &raw_track[RAW_TRACK_BYTES]) + write_byte( 0xff ); + + if ( fwrite(raw_track, 1, RAW_TRACK_BYTES, nib_file) != RAW_TRACK_BYTES) { + fprintf(stderr, "Error writing .nib file\n"); + exit(1); + } + } + + fclose(disk_file); + fclose(nib_file); + + return 0; +} + +/* Local Variables: */ +/* compile-command: "cc -O -Wall -pedantic -ansi -o dsk2nib dsk2nib.c" */ +/* End: */