--------------------------------------------------------------------------------------------------------- -- -- Name: cmt.vhd -- Created: July 2018 -- Author(s): Philip Smart -- Description: Sharp MZ series PWM Tape Interface. -- This module fully emulates the Sharp PWM tape interface. It uses cache ram -- to simulate the tape. Data is played out to the Sharp or read from the Sharp -- and stored in the ram. -- For reading of data from Tape to the Sharp, the HPS or other controller loads a -- complete tape into ram and should the Play/Auto function be enabled, playback -- starts immediately. -- For writing of data from the Sharp to Tape, the data is stored in ram and the -- HPS or other controller, when it gets the completed signal, can read out the ram -- and store the data onto a local filesystem. -- Credits: -- Copyright: (c) 2018 Philip Smart -- -- History: July 2018 - Initial module written and playback mode tested and debugged. -- August 2018 - Record mode written but not yet debugged/completed. -- October 2018 - Added APSS for MZ80B emulation. -- Major rework of read (MZ Write) logic. -- --------------------------------------------------------------------------------------------------------- -- This source file is free software: you can redistribute it and-or modify -- it under the terms of the GNU General Public License as published -- by the Free Software Foundation, either version 3 of the License, or -- (at your option) any later version. -- -- This source file 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 General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . --------------------------------------------------------------------------------------------------------- library ieee; library pkgs; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; use pkgs.config_pkg.all; use pkgs.clkgen_pkg.all; use pkgs.mctrl_pkg.all; use ieee.numeric_std.all; entity cmt is Port ( RST : in std_logic; -- Clock signals needed by this module. CLKBUS : in std_logic_vector(CLKBUS_WIDTH); -- Different operations modes. CONFIG : in std_logic_vector(CONFIG_WIDTH); -- Cassette magnetic tape signals. CMT_BUS_OUT : out std_logic_vector(CMT_BUS_OUT_WIDTH); CMT_BUS_IN : in std_logic_vector(CMT_BUS_IN_WIDTH); -- HPS Interface IOCTL_DOWNLOAD : in std_logic; -- HPS Downloading to FPGA. IOCTL_UPLOAD : in std_logic; -- HPS Uploading from FPGA. IOCTL_CLK : in std_logic; -- HPS I/O Clock. IOCTL_WR : in std_logic; -- HPS Write Enable to FPGA. IOCTL_RD : in std_logic; -- HPS Read Enable from FPGA. IOCTL_ADDR : in std_logic_vector(24 downto 0); -- HPS Address in FPGA to write into. IOCTL_DOUT : in std_logic_vector(31 downto 0); -- HPS Data to be written into FPGA. IOCTL_DIN : out std_logic_vector(31 downto 0); -- HPS Data to be read into HPS. -- Debug Status Leds DEBUG_STATUS_LEDS : out std_logic_vector(31 downto 0) -- 32 leds to display cmt internal status. ); end cmt; architecture RTL of cmt is -- -- Components -- component dpram generic ( init_file : string; widthad_a : natural; width_a : natural; widthad_b : natural; width_b : natural; outdata_reg_a : string := "UNREGISTERED"; outdata_reg_b : string := "UNREGISTERED" ); Port ( clock_a : in std_logic := '1'; clocken_a : in std_logic := '1'; address_a : in std_logic_vector (widthad_a-1 downto 0); data_a : in std_logic_vector (width_a-1 downto 0); wren_a : in std_logic := '0'; q_a : out std_logic_vector (width_a-1 downto 0); clock_b : in std_logic; clocken_b : in std_logic := '1'; address_b : in std_logic_vector (widthad_b-1 downto 0); data_b : in std_logic_vector (width_b-1 downto 0); wren_b : in std_logic := '0'; q_b : out std_logic_vector (width_b-1 downto 0) ); end component; -- HPS Control signals. signal IOCTL_CS_HDR_n : std_logic; signal IOCTL_CS_DATA_n : std_logic; signal IOCTL_CS_ASCII_n : std_logic; signal IOCTL_TAPEHDR_WEN : std_logic; signal IOCTL_TAPEDATA_WEN : std_logic; signal IOCTL_ASCII_WEN : std_logic; signal IOCTL_DIN_HDR : std_logic_vector(7 downto 0); signal IOCTL_DIN_DATA : std_logic_vector(7 downto 0); signal IOCTL_DIN_ASCII : std_logic_vector(7 downto 0); -- CMT Control signals. signal CMT_BUS_OUTi : std_logic_vector(CMT_BUS_OUT_WIDTH); -- CMT bus output. signal BUTTONS_LAST : std_logic_vector(1 downto 0); -- Virtual buttons last sample, used to detect changes. signal PLAY_READY_SET_CNT : integer range 0 to 32000000 := 0; -- 1 second timer from last cache upload to PLAY_READY being set. signal PLAY_READY_CLR_CNT : unsigned(21 downto 0); -- 2 second timer from motor being stopped to PLAY_READY being cleared. signal PLAY_READY : std_logic; -- Cache loaded, playback ready to commence. signal PLAY_READY_CLR : std_logic; -- Clear PLAY_READY signal. signal PLAY_BUTTON : std_logic; -- Virtual Play button. signal PLAYING : std_logic_vector(2 downto 0); -- Playing state, 3 cycle, 0 = inactive, 1 = active, msb = most recent. signal RECORD_READY : std_logic; -- Record buffer full (data received from MZ). Active = 1 signal RECORD_READY_SET : std_logic; -- Trigger to activate the RECORD_READY signal. signal RECORD_READY_SEQ : std_logic_vector(1 downto 0); -- Setup and hold sequencer. signal RECORD_BUTTON : std_logic; -- Virtual Record button. signal RECORDING : std_logic; -- Signal indicating a Record is underway, Active = 1. signal RECSEQ : std_logic_vector(2 downto 0); -- Signal, 3 cycles, indicating signal MOTOR_TOGGLE : std_logic_vector(1 downto 0); -- Signal indicating if the MZ wants to start or toggle the motor. signal APSS_TIMER_CNT : unsigned(20 downto 0); -- 1 second virtual APSS SEEK time. signal WRITEBIT : std_logic; -- Tape data signal sent to the MZ (for playback). signal READBIT : std_logic; -- Tape data signal eminating from the MZ (for recording). signal TAPE_MOTOR_ON_n : std_logic; -- Virtual motor signal, tape motor running = 0. -- Bit transmitter signals. signal XMIT_DONE : std_logic; -- Transmit of bit complete. signal XMIT_SEQ : std_logic_vector(2 downto 0); -- Setup and hold sequencer. signal XMIT_LOAD_1 : std_logic; -- Load bit and start transmission selector 1. signal XMIT_LOAD_2 : std_logic; -- Load bit and start transmission selector 2. signal XMIT_BIT_1 : std_logic; -- Transmit bit for XMIT_LOAD_2 to be PWM modulated and sent to MZ. signal XMIT_BIT_2 : std_logic; -- Working bit for active XMIT_BIT. signal XMIT_COUNT : integer range -7999 to 8000 := 0; signal XMIT_LIMIT : integer range -7999 to 8000 := 0; -- Bit padding transmitter signals. signal XMIT_PADDING_LOAD : std_logic; signal XMIT_PADDING_BIT : std_logic; signal XMIT_PADDING_DONE : std_logic; signal XMIT_PADDING_CNT1 : integer range 0 to 32767 := 0; signal XMIT_PADDING_CNT2 : integer range 0 to 32767 := 0; signal XMIT_PADDING_LEVEL1 : std_logic; signal XMIT_PADDING_LEVEL2 : std_logic; signal PADDING_SEQ : std_logic_vector(2 downto 0); -- Setup and hold sequencer. signal PADDING_CNT1 : integer range 0 to 32767 := 0; signal PADDING_CNT2 : integer range 0 to 32767 := 0; signal PADDING_LEVEL1 : std_logic; signal PADDING_LEVEL2 : std_logic; -- Cache RAM header/data transmitter signals. signal XMIT_RAM_LOAD : std_logic; signal XMIT_RAM_DONE : std_logic; signal XMIT_RAM_SEQ : std_logic_vector(2 downto 0); -- Setup and hold sequencer. signal XMIT_RAM_ADDR : std_logic_vector(15 downto 0); signal XMIT_ASCII_RAM_ADDR : std_logic_vector(8 downto 0); -- Address for the Sharp Ascii to Ascii conversion table. signal XMIT_RAM_COUNT : unsigned(15 downto 0); signal XMIT_RAM_CHKSUM_CNT : unsigned(1 downto 0); signal XMIT_RAM_CHECKSUM : std_logic_vector(15 downto 0); signal XMIT_RAM_TYPE : std_logic; signal XMIT_RAM_STATE : integer range 0 to 7 := 0; signal XMIT_RAM_SR : std_logic_vector(8 downto 0); signal XMIT_RAM_BIT_CNT : integer range 0 to 8 := 0; signal XMIT_TAPE_SIZE : unsigned(15 downto 0); -- RAM control signals. signal RAM_ADDR : std_logic_vector(15 downto 0); -- Multiplexed (Header, Data) RAM address signal RAM_DATAIN : std_logic_vector(7 downto 0); -- Multiplexed RAM data in. signal HDR_RAM_DATAOUT : std_logic_vector(7 downto 0); -- Header RAM data output. signal HDR_RAM_WEN : std_logic; -- Header RAM data write enable. signal DATA_RAM_DATAOUT : std_logic_vector(7 downto 0); -- Data RAM data output. signal DATA_RAM_WEN : std_logic; -- Data RAM data write enable. signal ASCII_RAM_ADDR : std_logic_vector(8 downto 0); -- Multiplexed RAM address for the Ascii conversion table. signal ASCII_RAM_DATAOUT : std_logic_vector(7 downto 0); -- Sharp Ascii to Ascii conversion output. -- -- Main process Finite State Machine variables. signal TAPE_READ_STATE : integer range 0 to 15 := 0; signal TAPE_READ_SEQ : std_logic_vector(2 downto 0); -- Setup and hold sequencer. -- Receiver (for recording) signals. signal RCV_RAM_SUCCESS : std_logic; signal RCV_RAM_ADDR : std_logic_vector(15 downto 0); signal RCV_ASCII_RAM_ADDR : std_logic_vector(8 downto 0); -- Address for the Sharp Ascii to Ascii conversion table. signal RCV_RAM_STATE : integer range 0 to 8 := 0; signal RCV_RAM_CHECKSUM : std_logic_vector(15 downto 0); signal RCV_TAPE_SIZE : std_logic_vector(15 downto 0); signal RCV_RAM_TRY : integer range 0 to 1; signal RCV_BYTE_AVAIL : std_logic; signal RCV_BYTE_CLR : std_logic; signal RCV_BYTE : std_logic_vector(7 downto 0); signal RCV_LOAD : std_logic; signal RCV_DONE : std_logic; signal RCV_CLR : std_logic; signal RCV_ERROR : std_logic; signal RCV_STATE : integer range 0 to 9; signal RCV_BLOCK : integer range 0 to 1; signal RCV_TYPE : integer range 0 to 1; signal RCV_TMH_CNT : integer range 0 to 40; signal RCV_TML_CNT : integer range 0 to 40; signal RCV_DATASIZE : unsigned(15 downto 0); signal RCV_SR : std_logic_vector(7 downto 0); signal RCV_CHECKSUM : std_logic_vector(15 downto 0); signal RCV_SEQ : std_logic_vector(1 downto 0); -- Setup and hold sequencer. signal RCV_CNT : unsigned(15 downto 0); signal TMH_CNT : integer range 0 to 40; signal TML_CNT : integer range 0 to 40; signal DATA_CNT : unsigned(15 downto 0); signal SPC_CNT : integer range 0 to 256; signal BIT_CNT : unsigned(2 downto 0); -- begin -- Wired signals between this CMT unit and the MZ/MCtrl bus. -- CMT_BUS_OUTi(pkgs.mctrl_pkg.WRITEBIT) <= WRITEBIT; -- Write a bit to the MZ PIO. CMT_BUS_OUTi(pkgs.mctrl_pkg.SENSE) <= not TAPE_MOTOR_ON_n; -- Indiate current state of Motor, 0 if not running, 1 if running. CMT_BUS_OUTi(pkgs.mctrl_pkg.ACTIVE) <= PLAYING(2) or RECORDING; CMT_BUS_OUTi(pkgs.mctrl_pkg.PLAY_READY) <= PLAY_READY; CMT_BUS_OUTi(pkgs.mctrl_pkg.PLAYING) <= PLAYING(2); CMT_BUS_OUTi(pkgs.mctrl_pkg.RECORD_READY) <= RECORD_READY; CMT_BUS_OUTi(pkgs.mctrl_pkg.RECORDING) <= RECORDING; -- READBIT <= CMT_BUS_IN(pkgs.mctrl_pkg.READBIT); -- Read a bit from the MZ PIO. MOTOR_TOGGLE(1) <= CMT_BUS_IN(PLAY); CMT_BUS_OUT <= CMT_BUS_OUTi; -- Mux Signals from different sources. RAM_ADDR <= RCV_RAM_ADDR when RECORDING = '1' else XMIT_RAM_ADDR; ASCII_RAM_ADDR <= RCV_ASCII_RAM_ADDR when RECORDING = '1' else XMIT_ASCII_RAM_ADDR; IOCTL_CS_HDR_n <= '0' when IOCTL_ADDR(24 downto 16) = "001000000" else '1'; IOCTL_CS_DATA_n <= '0' when IOCTL_ADDR(24 downto 16) = "001000001" else '1'; IOCTL_CS_ASCII_n <= '0' when IOCTL_ADDR(24 downto 16) = "001000010" else '1'; IOCTL_TAPEHDR_WEN <= '1' when IOCTL_CS_HDR_n = '0' and IOCTL_WR = '1' else '0'; IOCTL_TAPEDATA_WEN <= '1' when IOCTL_CS_DATA_n = '0' and IOCTL_WR = '1' else '0'; IOCTL_ASCII_WEN <= '1' when IOCTL_CS_ASCII_n = '0' and IOCTL_WR = '1' else '0'; IOCTL_DIN <= X"000000" & IOCTL_DIN_HDR when IOCTL_CS_HDR_n = '0' else X"000000" & IOCTL_DIN_DATA when IOCTL_CS_DATA_n = '0' else X"000000" & IOCTL_DIN_ASCII when IOCTL_CS_ASCII_n = '0' else (others => '0'); -- Header Cache RAM. -- Storage of the tape header for play and record operations. TAPEHDR : dpram GENERIC MAP ( init_file => null, widthad_a => 7, width_a => 8, widthad_b => 7, width_b => 8 ) PORT MAP ( clock_a => CLKBUS(CKMASTER), --CLKBUS(CKMEM), clocken_a => '1', address_a => RAM_ADDR(6 downto 0), data_a => RAM_DATAIN, wren_a => HDR_RAM_WEN, q_a => HDR_RAM_DATAOUT, clock_b => IOCTL_CLK, address_b => IOCTL_ADDR(6 downto 0), data_b => IOCTL_DOUT(7 downto 0), wren_b => IOCTL_TAPEHDR_WEN, q_b => IOCTL_DIN_HDR ); -- Data Cache RAM. -- Storage of the tape data for play and record operations. -- Maximum size of 64K as this is the limit that can be accommodated by the MZ software. TAPEDATA : dpram GENERIC MAP ( init_file => null, widthad_a => 16, width_a => 8, widthad_b => 16, width_b => 8 ) PORT MAP ( clock_a => CLKBUS(CKMASTER), --CLKBUS(CKMEM), clocken_a => '1', address_a => RAM_ADDR, data_a => RAM_DATAIN, wren_a => DATA_RAM_WEN, q_a => DATA_RAM_DATAOUT, clock_b => IOCTL_CLK, address_b => IOCTL_ADDR(15 downto 0), data_b => IOCTL_DOUT(7 downto 0), wren_b => IOCTL_TAPEDATA_WEN, q_b => IOCTL_DIN_DATA ); -- Sharp Ascii <-> Ascii conversion table. -- Filenames are generally in Sharp Ascii format which is incompatible with modern -- systems, ie. name of files on a file system, so conversion is needed in both directions. ADCNV : dpram GENERIC MAP ( init_file => "./software/mif/ascii_conv.mif", widthad_a => 9, width_a => 8, widthad_b => 9, width_b => 8 ) PORT MAP ( clock_a => CLKBUS(CKMASTER), --CLKBUS(CKMEM), clocken_a => '1', address_a => ASCII_RAM_ADDR(8 downto 0), data_a => (others => '0'), wren_a => '0', q_a => ASCII_RAM_DATAOUT, clock_b => IOCTL_CLK, address_b => IOCTL_ADDR(8 downto 0), data_b => IOCTL_DOUT(7 downto 0), wren_b => IOCTL_ASCII_WEN, q_b => IOCTL_DIN_ASCII ); -- MZ80B/2000 control signals. -- -- A0 MOTORON Activates reel motor -- A1 DIRECTION Prepares for FF state (prepares for REW with L) -- A2 PLAY plays cassette -- A3 STOP Stops casette operation -- B4 WRITEREADY Pawl applied to prohibit writing casette tape -- B5 TAPEREADY Indicates tape is set in the casette deck -- B6 WRITEBIT Input terminal for casette data -- B7 BREAKDETECT Detects break key during casette play -- C4 EJECT Starts eject operation -- C5 SEEK Latches ready state for FF and REW -- C6 WRITEENABLE Sets head amp to READ state (WRITE with L) -- C7 READBIT Outpus data to be written into casette -- -- DIRECTION clocked by SEEK, if DIRECTION = L when SEEK pulses high, then tape rewinds on activation of MOTORON. -- If DIRECTION is high, then tape will fast forward. MOTORON, when pulsed high, activates the motor to go forward/backward. -- PLAY pulsed high activates the play motor.which cancels a FF/REW event. -- STOP when High, stops the Play/FF/REW events. -- TAPEREADY when high indicates tape drive present and ready. -- EJECT when Low, ejects the tape. -- WRITEENABLE when Low enables record, otherwise when High enables play. -- READBIT is the data to write to tape. -- WRITEBIT is the data read from tape. -- WRITEREADY when Low blocks recording. -------------------------------------------------------------------- -- TAPE HEADER FORMAT -------------------------------------------------------------------- -- LGAP | LTM | L | HDR | CHKH | L | 256S | HDRC | CHKH | L -- SGAP | STM | L | FILE | CHKF | L | 256S | FILEC | CHKF | L -- LGAP is a long GAP -- SGAP is a short GAP -- LTM is a long tapemark - 40 long pulses then 40 short pulses -- STM is a short tapemark - 20 long pulses then 20 short puses -- HDR is the tapeheader -- HDRC is a copy of the tapeheader -- FILE is the file -- FILEC is a copy of the file -- CHKH is a 2 byte checksum of the tape header or its copy -- CHKF is a 2 byte checksum of the file or its copy -- L is 1 long pulse -- 256S contains 256 short pulses -------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------- CMT control --------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------- -- Process to determine CMT state according to inputs. ie. If we are STOPPED, PLAYING or RECORDING. -- This is determined by the input switches in CONFIG(BUTTOS), 00 = Off, 01 = Play, 02 = Record and 03 = Auto. -- Auto mode indicates the CMT logic has to determine wether it is PLAYING or RECORDING. The default is PLAYING -- but if a bit is received from the MZ then we switch to RECORDING until a full tape dump has been received. -- process( RST, CLKBUS(CKMASTER), CONFIG(BUTTONS), MOTOR_TOGGLE, CMT_BUS_IN) begin if RST='1' then TAPE_MOTOR_ON_n <= '1'; PLAY_BUTTON <= '0'; RECORD_BUTTON <= '0'; BUTTONS_LAST <= "00"; PLAYING <= "000"; RECORDING <= '0'; MOTOR_TOGGLE(0) <= '0'; APSS_TIMER_CNT <= (others => '0'); CMT_BUS_OUTi(APSS_SEEK) <= '0'; CMT_BUS_OUTi(APSS_DIR) <= '0'; CMT_BUS_OUTi(APSS_EJECT) <= '0'; CMT_BUS_OUTi(APSS_PLAY) <= '0'; CMT_BUS_OUTi(APSS_STOP) <= '1'; elsif CLKBUS(CKMASTER)'event and CLKBUS(CKMASTER)='1' then if CLKBUS(CKENCPU) = '1' then -- Store last state so we detect change. BUTTONS_LAST <= CONFIG(BUTTONS); MOTOR_TOGGLE(0) <= MOTOR_TOGGLE(1); -- Store last state so we can detect a switch to recording or play mode. PLAYING(1 downto 0) <= PLAYING(2 downto 1); -- The MZ80C series use a manual cassette deck with motor automation, so we -- need to simulate buttons and the states therein. -- if CONFIG(MZ_80C) = '1' then -- Process the buttons and adapt signals accordingly. -- if BUTTONS_LAST /= CONFIG(BUTTONS) then case CONFIG(BUTTONS) is when "00" => -- Off PLAY_BUTTON <= '0'; RECORD_BUTTON <= '0'; TAPE_MOTOR_ON_n <= '1'; CMT_BUS_OUTi(TAPEREADY) <= '1'; -- Indicates tape ejected. CMT_BUS_OUTi(WRITEREADY)<= '1'; -- Indicates write mechanism disabled. when "10" => -- Record PLAY_BUTTON <= '0'; RECORD_BUTTON <= '1'; TAPE_MOTOR_ON_n <= '0'; CMT_BUS_OUTi(TAPEREADY) <= '0'; -- Indicates tape loaded, active Low. CMT_BUS_OUTi(WRITEREADY)<= '1'; -- Indicates write mechanism enabled. when "01"|"11" => -- Play/Auto -- Assume playback mode for Auto unless activity is detected from the MZ, -- in which case switch to Recording. PLAY_BUTTON <= '1'; RECORD_BUTTON <= '0'; TAPE_MOTOR_ON_n <= '0'; CMT_BUS_OUTi(TAPEREADY) <= '0'; -- Indicates tape loaded, active Low. CMT_BUS_OUTi(WRITEREADY)<= '0'; -- Indicates write mechanism disabled. end case; end if; -- Once a recording becomes available to save, disable recording state. -- if RECORD_READY = '1' then RECORDING <= '0'; end if; -- If in auto mode and data starts being received from the MZ, enter record mode. Once record -- mode completes, switch back to play mode. -- if CONFIG(BUTTONS) = "11" then if RCV_SEQ = "11" or RECORDING = '1' then PLAY_BUTTON <= '0'; RECORD_BUTTON <= '1'; CMT_BUS_OUTi(WRITEREADY) <= '1'; -- Indicates write mechanism disabled. else PLAY_BUTTON <= '1'; RECORD_BUTTON <= '0'; end if; end if; -- If the motor is running then setup the state according to the buttons pressed and the data availability. -- if TAPE_MOTOR_ON_n = '0' and PLAY_BUTTON = '1' and PLAY_READY = '1' then PLAYING(2) <= '1'; RECORDING <= '0'; elsif TAPE_MOTOR_ON_n = '0' and RECORD_BUTTON = '1' then PLAYING(2) <= '0'; RECORDING <= '1'; else PLAYING(2) <= '0'; RECORDING <= '0'; end if; -- The play motor is controlled by an on/off toggle. A high pulse on MOTOR_TOGGLE will toggle the motor state. -- if MOTOR_TOGGLE = "10" then TAPE_MOTOR_ON_n <= not TAPE_MOTOR_ON_n; end if; -- The MZ80B uses a fully automated APSS cassette deck, so just take actions on -- the signals sent. -- else -- Tape is always ready and able to write. -- CMT_BUS_OUTi(TAPEREADY) <= '0'; -- Indicates tape loaded, active low. CMT_BUS_OUTi(WRITEREADY) <= '1'; -- Indicates write mechanism disabled. -- If seek pulses high, store the direction. -- if CMT_BUS_IN(pkgs.mctrl_pkg.SEEK) = '1' then CMT_BUS_OUTi(APSS_DIR) <= CMT_BUS_IN(pkgs.mctrl_pkg.DIRECTION); end if; -- If Eject goes active, latch and invert it for reading. -- if CMT_BUS_IN(pkgs.mctrl_pkg.EJECT) = '0' then CMT_BUS_OUTi(APSS_EJECT) <= '1'; CMT_BUS_OUTi(APSS_SEEK) <= '0'; CMT_BUS_OUTi(APSS_PLAY) <= '0'; CMT_BUS_OUTi(APSS_STOP) <= '0'; end if; -- The play motor is started/stopped by the PLAY/STOP signals. -- if MOTOR_TOGGLE = "11" and CMT_BUS_IN(pkgs.mctrl_pkg.STOP) = '0' then TAPE_MOTOR_ON_n <= '0'; CMT_BUS_OUTi(APSS_PLAY) <= '1'; CMT_BUS_OUTi(APSS_EJECT) <= '0'; CMT_BUS_OUTi(APSS_SEEK) <= '0'; CMT_BUS_OUTi(APSS_STOP) <= '0'; elsif MOTOR_TOGGLE /= "11" and CMT_BUS_IN(pkgs.mctrl_pkg.STOP) = '1' then TAPE_MOTOR_ON_n <= '1'; CMT_BUS_OUTi(APSS_STOP) <= '1'; CMT_BUS_OUTi(APSS_PLAY) <= '0'; CMT_BUS_OUTi(APSS_EJECT) <= '0'; CMT_BUS_OUTi(APSS_SEEK) <= '0'; -- If REEL_MOTOR pulses high, then engage APSS seek. -- elsif CMT_BUS_IN(REEL_MOTOR) = '1' then CMT_BUS_OUTi(APSS_SEEK) <= '1'; CMT_BUS_OUTi(APSS_EJECT) <= '0'; CMT_BUS_OUTi(APSS_PLAY) <= '0'; CMT_BUS_OUTi(APSS_STOP) <= '0'; APSS_TIMER_CNT <= to_unsigned(1, 21); end if; -- If the APSS SEEK action has been started, reset it after 1 second to simulate the real action. -- if APSS_TIMER_CNT > 0 then APSS_TIMER_CNT <= APSS_TIMER_CNT + 1; end if; if APSS_TIMER_CNT = X"FFFFF" then CMT_BUS_OUTi(APSS_SEEK) <= '0'; end if; -- Update the status as to wether we are playing, recording or idle. -- PLAYING(2) <= PLAY_READY and CMT_BUS_OUTi(APSS_PLAY); RECORDING <= not CMT_BUS_IN(pkgs.mctrl_pkg.WRITEENABLE) and CMT_BUS_OUTi(APSS_PLAY); end if; end if; end if; end process; -- Trigger, when a write occurs to ram, start a counter. Each write resets the counter. After 1 second of -- no further writes, then the ram data is ready to play. -- Clear funtionality allows the logic to clear the ready signal to indicate data has been processed. -- process( RST, IOCTL_CLK, IOCTL_TAPEHDR_WEN, IOCTL_TAPEDATA_WEN, IOCTL_CS_HDR_n, IOCTL_CS_DATA_n ) begin if RST = '1' then PLAY_READY <= '0'; PLAY_READY_SET_CNT <= 0; RECORD_READY <= '0'; RECORD_READY_SEQ <= "00"; elsif IOCTL_CLK'event and IOCTL_CLK = '1' then -- Sample record complete signal and hold. Shift righ 2 bits, msb = latest value. RECORD_READY_SEQ(0) <= RECORD_READY_SEQ(1); RECORD_READY_SEQ(1) <= RECORD_READY_SET; -- If the external clear is triggered, reset ready signal. if PLAY_READY_CLR = '1' then PLAY_READY <= '0'; PLAY_READY_SET_CNT <= 0; -- Every write to ram resets the counter. elsif IOCTL_TAPEHDR_WEN = '1' or IOCTL_TAPEDATA_WEN = '1' then PLAY_READY <= '0'; PLAY_READY_SET_CNT <= 1; -- 1 second timer, if no new writes have occurred to RAM, then set the ready flag. elsif PLAY_READY_SET_CNT >= 32000000 then PLAY_READY_SET_CNT <= 0; PLAY_READY <= '1'; end if; -- Set RECORD_READY if fsm determines a full tape message received. if RECORD_READY_SEQ = "10" then PLAY_READY <= '0'; RECORD_READY <= '1'; -- HPS access resets signal. elsif IOCTL_CS_HDR_n = '0' or IOCTL_CS_DATA_n = '0' then RECORD_READY <= '0'; -- If the fsm resets the signal then clear the flag as it will be receiving a new tape message. elsif RECORD_READY_SEQ = "00" then RECORD_READY <= '0'; end if; -- Increment counters if enabled. if PLAY_READY_SET_CNT >= 1 then PLAY_READY_SET_CNT <= PLAY_READY_SET_CNT + 1; end if; end if; end process; ----------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------- Read from MZ (Write to Tape) logic --------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------- -- Write To Tape logic. -- -- This block concentrates all the logic required to receive data from the MZ into RAM (virtual tape). -- Viewed from the CMT, it is the reception of data from computer onto tape. ---------------------------------------------------------------------------------------------------------------------- -- Process to receive the header/data blocks and checksum from the MZ and store it into the cache RAM. ie. MZ -> RAM. -- The bit stream is assumed to be at the correct point and the data is serialised and loaded into the RAM. -- process( RST, CLKBUS(CKMASTER) ) begin if RST = '1' then RCV_RAM_SUCCESS <= '0'; RCV_RAM_ADDR <= (others => '0'); RCV_RAM_CHECKSUM <= (others => '0'); RCV_RAM_STATE <= 0; RCV_RAM_TRY <= 0; RCV_LOAD <= '0'; RCV_CLR <= '1'; RCV_BYTE_CLR <= '0'; RCV_TMH_CNT <= 0; RCV_TML_CNT <= 0; RCV_DATASIZE <= to_unsigned(0, 16); RCV_TAPE_SIZE <= (others => '0'); RECORD_READY_SET <= '0'; elsif CLKBUS(CKMASTER)'event and CLKBUS(CKMASTER) = '1' then if CLKBUS(CKENCPU) = '1' then -- Store the recording state to trigger events on changes. RECSEQ(1 downto 0) <= RECSEQ(2 downto 1); RECSEQ(2) <= RECORDING; -- If Receiver clear state is active, reset, only active for one clock. -- if RCV_CLR = '1' then RCV_CLR <= '0'; end if; -- If a load command was executed, when the DONE signal goes inactive, acknowledging the command, reset -- the load signal. if RCV_LOAD = '1' and RCV_DONE = '0' then RCV_LOAD <= '0'; end if; -- If an error occurs, reset to the very beginning. if RCV_ERROR = '1' then RCV_RAM_STATE <= 1; RCV_TYPE <= 0; RCV_CLR <= '1'; end if; -- At the end of a recording run, make a full reset ready for next save. -- if RECSEQ = "001" then RCV_RAM_SUCCESS <= '0'; RCV_RAM_ADDR <= (others => '0'); RCV_RAM_CHECKSUM <= (others => '0'); RCV_RAM_STATE <= 0; RCV_RAM_TRY <= 0; RCV_LOAD <= '0'; RCV_CLR <= '1'; RCV_BYTE_CLR <= '0'; RCV_TMH_CNT <= 0; RCV_TML_CNT <= 0; RCV_DATASIZE <= to_unsigned(0, 16); -- If recording mode starts, setup required state. elsif RECSEQ = "100" then RECORD_READY_SET <= '0'; RCV_RAM_STATE <= 1; -- If recording, run the FSM. elsif RECSEQ = "111" then -- FSM to implement the receiption of data from MZ and storage in cache RAM> case(RCV_RAM_STATE) is when 0 => RCV_RAM_STATE <= 0; -- Load up parameters for the Header or Data block according to expected type. when 1 => if RCV_TYPE = 0 then RCV_TMH_CNT <= 40; RCV_TML_CNT <= 40; RCV_DATASIZE <= to_unsigned(128 + 2, 16); -- 2 Additional bytes for the checksum. else RCV_TMH_CNT <= 20; RCV_TML_CNT <= 20; RCV_DATASIZE <= unsigned(RCV_TAPE_SIZE + 2); end if; RCV_RAM_SUCCESS <= '0'; RCV_RAM_ADDR <= (others => '0'); RCV_RAM_CHECKSUM <= (others => '0'); RCV_RAM_STATE <= 2; RCV_RAM_TRY <= 0; RCV_LOAD <= '1'; RCV_BYTE_CLR <= '0'; HDR_RAM_WEN <= '0'; DATA_RAM_WEN <= '0'; -- As data bytes become available, assemble them into RAM. The last -- 2 bytes are the checksum. If this is the header, then byes 18 and 19 -- are the data block size to be received next. when 2 => if RCV_BYTE_AVAIL = '1' then RCV_BYTE_CLR <= '1'; -- During header reception, bytes 18 and 19 represent the size of the data segment. -- if RCV_TYPE = 0 and RCV_RAM_SUCCESS = '0' then if RCV_RAM_ADDR = 18 then RCV_TAPE_SIZE(7 downto 0) <= RCV_BYTE; elsif RCV_RAM_ADDR = 19 then RCV_TAPE_SIZE(15 downto 8) <= RCV_BYTE; end if; end if; -- If all data received, then next 2 bytes are the checksums. if RCV_RAM_ADDR = std_logic_vector(RCV_DATASIZE - 2) then RCV_RAM_CHECKSUM(15 downto 8) <= RCV_BYTE; RCV_RAM_STATE <= 6; elsif RCV_RAM_ADDR = std_logic_vector(RCV_DATASIZE - 1) then RCV_RAM_CHECKSUM(7 downto 0) <= RCV_BYTE; if RCV_RAM_TRY = 0 then RCV_RAM_STATE <= 8; else RCV_RAM_STATE <= 7; end if; elsif RCV_RAM_TRY = 1 and RCV_RAM_SUCCESS = '1' then RCV_RAM_STATE <= 6; -- If enabled, convert the filename to standard ascii. elsif RCV_TYPE = 0 and CONFIG(MZ_80C) = '1' and CONFIG(CMTASCII_IN) = '1' and RCV_RAM_ADDR >= std_logic_vector(to_unsigned(1, 9)) and RCV_RAM_ADDR <= std_logic_vector(to_unsigned(17,9)) then RCV_ASCII_RAM_ADDR <= '0' & RCV_BYTE; RCV_RAM_STATE <= 3; else RAM_DATAIN <= RCV_BYTE; RCV_RAM_STATE <= 4; end if; end if; -- Byte to be written is mapped via the Sharp Ascii <-> Ascii lookup table. when 3 => RAM_DATAIN <= ASCII_RAM_DATAOUT; RCV_RAM_STATE <= 4; -- Assert Write to load data into RAM. when 4 => if RCV_TYPE = 0 then HDR_RAM_WEN <= '1'; else DATA_RAM_WEN <= '1'; end if; RCV_RAM_STATE <= 5; -- Deassert write. when 5 => HDR_RAM_WEN <= '0'; DATA_RAM_WEN <= '0'; RCV_RAM_STATE <= 6; when 6 => -- Once write transaction has completed, update the RAM address. RCV_RAM_ADDR <= RCV_RAM_ADDR + 1; -- Receive the next byte. RCV_RAM_STATE <= 2; when 7 => if RCV_DONE = '1' then RCV_RAM_STATE <= 8; end if; -- Compare checksums, raise SUCCESS flag if they match. when 8 => if RCV_RAM_SUCCESS = '0' and RCV_RAM_CHECKSUM = RCV_CHECKSUM then RCV_RAM_SUCCESS <= '1'; end if; if RCV_RAM_TRY = 0 then RCV_RAM_TRY <= 1; RCV_RAM_ADDR <= (others => '0'); RCV_RAM_STATE <= 2; elsif RCV_TYPE = 0 and RCV_DONE = '1' then RCV_RAM_TRY <= 0; RCV_TYPE <= 1; -- Receive data RCV_RAM_STATE <= 1; RECSEQ <= "001"; --RCV_CLR <= '1'; else RCV_RAM_STATE <= 1; -- Start waiting for a new header. RCV_CLR <= '1'; RCV_TYPE <= 0; -- Receive header RECORD_READY_SET <= '1'; end if; end case; end if; end if; end if; end process; -- Process to read a bit (PWM decode) from the MZ output and process it according to the expected MZ Tape format framing. -- Basically we detect when the bit rises then start counting a fixed period of time. Once the period has -- elapsed, we sample the data and indicate it is available. -- The bit is then fed through an FSM which evaluates the value and the position in order to setup the correct framing and -- commence data extraction. The data is then provided byte by byte to the external process which assembles it into memory -- and checks the checksums. -- process( RST, CLKBUS(CKMASTER), READBIT ) begin if RST = '1' then RCV_CNT <= (others => '0'); RCV_SEQ <= "00"; RCV_ERROR <= '0'; RCV_STATE <= 0; RCV_DONE <= '1'; RCV_BLOCK <= 0; RCV_BYTE_AVAIL <= '0'; RCV_BYTE <= (others => '0'); RCV_SR <= (others => '0'); RCV_CHECKSUM <= (others => '0'); TMH_CNT <= 0; TML_CNT <= 0; DATA_CNT <= (others => '0'); SPC_CNT <= 0; BIT_CNT <= (others => '0'); elsif CLKBUS(CKMASTER)'event and CLKBUS(CKMASTER) = '1' then if CLKBUS(CKENCPU) = '1' then -- Sample incoming bit and hold. Detect when a valid transmission starts. RCV_SEQ(0) <= RCV_SEQ(1); RCV_SEQ(1) <= READBIT; -- Clear byte available flag? -- if RCV_BYTE_CLR = '1' then RCV_BYTE_AVAIL <= '0'; end if; -- Countdown measurement timer until till we reach 0 to take a sample. if RCV_CNT > 0 then RCV_CNT <= RCV_CNT - 1; end if; -- If external request made to read a tape block, sample the parameters and set the state machine running. -- Same is applicable for a clear, when we receive the clear, reload the parameters and run from start. -- if RCV_LOAD = '1' or RCV_CLR = '1' then RCV_DONE <= '0'; RCV_CNT <= (others => '0'); RCV_SEQ <= (others => '0'); RCV_ERROR <= '0'; if RCV_CLR = '1' then RCV_STATE <= 0; else RCV_STATE <= 1; end if; RCV_BLOCK <= 0; TMH_CNT <= RCV_TMH_CNT; TML_CNT <= RCV_TML_CNT; DATA_CNT <= RCV_DATASIZE; SPC_CNT <= 256; -- 256 short pulses (0) between data blocks. BIT_CNT <= "111"; -- 7 .. 0 = 8 bits RCV_BYTE_AVAIL <= '0'; RCV_BYTE <= (others => '0'); RCV_SR <= (others => '0'); RCV_CHECKSUM <= (others => '0'); end if; -- A rising edge on the incoming data line indicates the start of data. We measure in from this edge the following -- amount of time, then sample the bit as the 'read' value. -- if RCV_SEQ = "10" then -- Pulse periods for MZ80C type machines if CONFIG(MZ_KC) = '1' or CONFIG(MZ_A) = '1' then RCV_CNT <= to_unsigned(736, 16); -- 368uS @ 2Mhz elsif CONFIG(MZ700) = '1' then RCV_CNT <= to_unsigned(1302, 16); -- 368uS @ 3.54MHz else RCV_CNT <= to_unsigned(1020, 16); -- 255uS @ 4MHz end if; end if; -- Sample bit and set flag. if RCV_CNT = 1 then -- State machine clocked by reception of bits. -- case RCV_STATE is -- Parking state. when 0 => RCV_STATE <= 0; -- Long or Short Gap. Actual number is not so important as some bits can be lost on initial startup. -- The purpose of the gap is to synchronise and we use it to fine tune our sampling. when 1 => if READBIT = '1' then RCV_STATE <= 2; TMH_CNT <= TMH_CNT - 1; end if; -- Long or Short Tape Mark part 1. when 2 => if TMH_CNT > 0 and READBIT = '1' then TMH_CNT <= TMH_CNT - 1; if TMH_CNT = 1 then RCV_STATE <= 3; end if; elsif READBIT = '0' then RCV_ERROR <= '1'; RCV_STATE <= 0; end if; -- Long or Short Tape Mark part 2. when 3 => if TML_CNT > 0 and READBIT = '0' then TML_CNT <= TML_CNT - 1; if TML_CNT = 1 then RCV_STATE <= 4; end if; elsif READBIT = '1' then RCV_ERROR <= '1'; RCV_STATE <= 0; end if; -- Single long pulse. when 4 => if READBIT = '1' then RCV_CHECKSUM <= (others => '0'); RCV_STATE <= 5; else RCV_ERROR <= '1'; RCV_STATE <= 0; end if; -- Each byte is preceded by a single long pulse. when 5 => if READBIT = '1' then RCV_STATE <= 6; else RCV_ERROR <= '1'; RCV_STATE <= 0; end if; -- Store 1 bit. when 6 => -- Count each 1 as sum represents the checksum. if READBIT = '1' and DATA_CNT > 2 then RCV_CHECKSUM <= RCV_CHECKSUM + 1; end if; RCV_SR <= RCV_SR(6 downto 0) & READBIT; BIT_CNT <= BIT_CNT - 1; -- If this was the last bit, make byte available and then move to next state. if BIT_CNT = "000" then RCV_BYTE <= RCV_SR(6 downto 0) & READBIT; RCV_BYTE_AVAIL <= '1'; DATA_CNT <= DATA_CNT - 1; -- All bytes been received? if DATA_CNT = 1 then RCV_STATE <= 8; else RCV_STATE <= 5; -- Back to get next byte. end if; end if; when 7 => -- Single long pulse. when 8 => if READBIT = '1' then -- If this is the second copy received, then finish. if RCV_BLOCK = 1 then RCV_STATE <= 0; RCV_DONE <= '1'; else RCV_STATE <= 9; end if; else RCV_ERROR <= '1'; RCV_STATE <= 9; -- Bit is less important, flag the error but assembler can check the checksum to verify. end if; -- 256 Short padding block to seperate the data blocks. when 9 => if SPC_CNT > 0 and READBIT = '0' then SPC_CNT <= SPC_CNT - 1; -- Last space byte, move to next block receipt. if SPC_CNT = 1 then RCV_BLOCK <= 1; DATA_CNT <= RCV_DATASIZE; RCV_STATE <= 5; -- Go back to retrieve second copy. end if; elsif READBIT = '1' then RCV_ERROR <= '1'; RCV_STATE <= 0; end if; end case; end if; end if; end if; end process; ----------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------- Write to MZ (Playback from Tape) logic --------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------- -- Read From Tape logic (write to MZ). -- Definitions: Read = Read from virtual tape (RAM). -- Write = Write into virtual tape (RAM). -- Xmit = Transmit from CMT to MZ. -- Rcv = Receive from MZ into CMT. -- Thus you read Read from tape and transmit to MZ, or Receive from MZ and write onto virtual tape. -- Playing is when the CMT is reading from tape, Recording is when the CMT is writing to tape. -- -- This block concentrates all the logic required to deliver data from RAM (virtual tape) to the MZ. -- Viewed from the CMT, it is the transmission of data from tape to computer. ---------------------------------------------------------------------------------------------------------------------- -- State machine to represent the tape drive READ mode (RAM -> MZ) using cache memory as the tape, which is populated by the HPS. -- process( RST, CLKBUS(CKMASTER), PLAYING ) begin -- For reset, hold machine in reset. if RST = '1' then PLAY_READY_CLR <= '0'; PLAY_READY_CLR_CNT <= (others => '0'); TAPE_READ_STATE <= 0; TAPE_READ_SEQ <= "000"; XMIT_PADDING_LOAD <= '0'; XMIT_RAM_LOAD <= '0'; XMIT_RAM_TYPE <= '0'; elsif CLKBUS(CKMASTER)'event and CLKBUS(CKMASTER) = '1' then if CLKBUS(CKENCPU) = '1' then -- 2 second after the tape motor goes off clear the PLAY_READY signal, indicating cache tape is no -- longer in use. -- if PLAY_READY_CLR_CNT = X"1EFFFF" then PLAY_READY_CLR <= '1'; -- A short time after activation (22 bits expiration), clear the reset signal so as to allow -- further loads to take place. -- elsif PLAY_READY_CLR_CNT = X"1FFFFF" then PLAY_READY_CLR_CNT <= (others => '0'); PLAY_READY_CLR <= '0'; end if; -- If the PLAY_READY reset timer is running (> 0), increment until we reach timeout. -- if PLAY_READY_CLR_CNT > 0 then PLAY_READY_CLR_CNT <= PLAY_READY_CLR_CNT + 1; end if; -- If playing has been suspended, on 3rd clock determine the next state, setup and clear necessary signals. if PLAYING = "001" then XMIT_PADDING_LOAD <= '0'; XMIT_RAM_LOAD <= '0'; PLAY_READY_CLR_CNT <= to_unsigned(1, 22); -- If the data block was received on first attempt, MZ will stop the motor, so skip the second block. if XMIT_RAM_TYPE = '0' and TAPE_READ_STATE > 6 and TAPE_READ_STATE < 15 then TAPE_READ_STATE <= 14; else TAPE_READ_STATE <= 15; end if; -- Change in play state, start fsm to play out the ram contents when the HPS upload has completed. elsif PLAYING = "110" then if TAPE_READ_STATE = 15 then TAPE_READ_STATE <= 0; XMIT_RAM_TYPE <= '0'; end if; PLAY_READY_CLR_CNT <= (others => '0'); -- If playing, run the FSM. elsif PLAYING = "111" then -- Sample the done signal, when setup and stable, we can continue. TAPE_READ_SEQ(1 downto 0) <= TAPE_READ_SEQ(2 downto 1); TAPE_READ_SEQ(2) <= (XMIT_PADDING_LOAD or XMIT_RAM_LOAD) and (XMIT_PADDING_DONE and XMIT_RAM_DONE); -- If a transmission has just been started and acknowledged by the DONE flag being reset, reset the activation strobe. -- if TAPE_READ_SEQ(0) = '1' then XMIT_PADDING_LOAD <= '0'; XMIT_RAM_LOAD <= '0'; end if; -- If a transmission is in progress, run the FSM. -- if XMIT_PADDING_LOAD = '0' and XMIT_RAM_LOAD = '0' then -- Default is to move onto next state per clock cycle, unless modified by the state action. TAPE_READ_STATE <= TAPE_READ_STATE + 1; -- Execute current state. case TAPE_READ_STATE is -- Section 1 - Header -- when 0 => -- Header = 0, Data = 1 if XMIT_RAM_TYPE = '0' then -- Setup to send a Long Gap. if CONFIG(MZ_80C) = '1' or CONFIG(MZ700) = '1' then XMIT_PADDING_CNT1<= 22000; else XMIT_PADDING_CNT1<= 10000; end if; else if CONFIG(MZ_80C) = '1' or CONFIG(MZ700) = '1' then XMIT_PADDING_CNT1<= 11000; else XMIT_PADDING_CNT1<= 10000; end if; end if; XMIT_PADDING_LEVEL1 <= '0'; -- Short Pulses XMIT_PADDING_CNT2 <= 0; XMIT_PADDING_LEVEL2 <= '0'; XMIT_PADDING_LOAD <= '1'; when 1 => -- Wait for the padding transmission to complete. if XMIT_PADDING_DONE = '0' then TAPE_READ_STATE <= 1; end if; when 2 => -- Header = 0, Data = 1 if XMIT_RAM_TYPE = '0' then -- Setup to send a Long Tape Mark. XMIT_PADDING_CNT1 <= 40; XMIT_PADDING_CNT2 <= 40; else -- Setup to send a Short Tape Mark. XMIT_PADDING_CNT1 <= 20; XMIT_PADDING_CNT2 <= 20; end if; XMIT_PADDING_LEVEL1 <= '1'; -- Long Pulses XMIT_PADDING_LEVEL2 <= '0'; -- Short Pulses XMIT_PADDING_LOAD <= '1'; when 3 => if XMIT_PADDING_DONE = '0' then TAPE_READ_STATE <= 3; end if; when 4 => -- Setup to send a Long Pulse. XMIT_PADDING_CNT1 <= 1; XMIT_PADDING_LEVEL1 <= '1'; -- Long Pulse XMIT_PADDING_CNT2 <= 0; XMIT_PADDING_LEVEL2 <= '0'; XMIT_PADDING_LOAD <= '1'; when 5 => if XMIT_PADDING_DONE = '0' then TAPE_READ_STATE <= 5; end if; -- Send the header and checksum for header. when 6 => XMIT_RAM_LOAD <= '1'; -- Send First copy of header/data. when 7 => if XMIT_RAM_DONE = '0' then -- If first copy successfully received, MZ will issue a motor stop. TAPE_READ_STATE <= 7; end if; when 8 => -- Setup to send 256 short pulse padding. XMIT_PADDING_CNT1 <= 256; XMIT_PADDING_LEVEL1 <= '0'; XMIT_PADDING_CNT2 <= 0; XMIT_PADDING_LEVEL2 <= '0'; XMIT_PADDING_LOAD <= '1'; when 9 => if XMIT_PADDING_DONE = '0' then TAPE_READ_STATE <= 9; end if; -- Resend the header/data as backup copy. when 10 => XMIT_RAM_LOAD <= '1'; -- If required, send second copy of header/data. when 11 => if XMIT_RAM_DONE = '0' then TAPE_READ_STATE <= 11; end if; when 12 => -- Setup to send a Long Pulse. XMIT_PADDING_CNT1 <= 1; XMIT_PADDING_LEVEL1 <= '1'; XMIT_PADDING_CNT2 <= 0; XMIT_PADDING_LEVEL2 <= '0'; XMIT_PADDING_LOAD <= '1'; when 13 => if XMIT_PADDING_DONE = '0' then TAPE_READ_STATE <= 13; end if; -- Switch to data if we have just transmitted the header, else terminate the process. when 14 => if XMIT_RAM_TYPE = '0' then XMIT_RAM_TYPE <= '1'; TAPE_READ_STATE <= 0; end if; -- Clear the Play Ready strobe and wait at this state until external actions reset the state. when 15 => TAPE_READ_STATE <= 15; end case; end if; end if; end if; end if; end process; -- Process to read the tape data blocks and checksum from RAM and transmit it to the MZ. -- -- The ram is serialised and written to the MZ. A checksum (count of 1's) is calculated and transmitted -- immediately after the data. -- XMIT_READ_DONE is high when the tape header and checksum transmission are complete. -- Normally, XMIT_RAM_LOAD is asserted high and then wait until XMIT_DONE goes high, finally deassert XMIT_RAM_LOAD to low. -- -- XMIT_LOAD_2 = = Load signal to commence bit transmission. -- XMIT_BIT_2 = = Input into bit transmitter of bit value to be sent. -- XMIT_RAM_DONE = = Transmission of RAM block complete (= 1). -- XMIT_RAM_TYPE = = 0 - Header, 1 = Data -- XMIT_RAM_ADDR = = Address of the RAM to be transmitted. RAM can be header or data ram block -- XMIT_RAM_COUNT = = Count of bytes to be sent, 0 = end. -- XMIT_RAM_CHECKSUM = = Sum of number of 1's transmitted. -- XMIT_RAM_STATE = = State machine current state. -- CLKBUS(CKMASTER) = = Base clock for encoding/decoding of pwm pulse. -- process( RST, CLKBUS(CKMASTER), XMIT_RAM_LOAD, XMIT_RAM_TYPE ) begin if RST = '1' then XMIT_RAM_DONE <= '1'; -- Default state is DONE, data transmitted. Set to 0 when transmission in progress. XMIT_LOAD_2 <= '0'; -- LOAD signal to the bit writer. 1 = start bit transmission. -- XMIT_BIT_2 <= '0'; -- Level of bit to transmit. XMIT_RAM_ADDR <= std_logic_vector(to_unsigned(0, 16)); -- Address of cache memory for next byte. XMIT_RAM_COUNT <= to_unsigned(0, 16); -- Count of bytes to transmit, excludes checksum. XMIT_RAM_CHKSUM_CNT <= to_unsigned(0, 2); -- Count of checksum bytes to transmit. XMIT_RAM_CHECKSUM <= std_logic_vector(to_unsigned(0, 16)); -- Calculated checksum, count of all 1's in data bytes. XMIT_RAM_STATE <= 0; -- FSM state. XMIT_RAM_SEQ <= "000"; elsif CLKBUS(CKMASTER)'event and CLKBUS(CKMASTER) = '1' then if CLKBUS(CKENCPU) = '1' then -- Sample load signal and hold. Shift right 3 bits, msb = latest value. XMIT_RAM_SEQ(1 downto 0) <= XMIT_RAM_SEQ(2 downto 1); XMIT_RAM_SEQ(2) <= XMIT_RAM_LOAD; -- If load is stable, acknowledge by bringing DONE low and start process. if XMIT_RAM_SEQ = "111" then XMIT_RAM_DONE <= '0'; -- When XMIT_RAM_LOAD is asserted and setled, sample parameters, set address and count for the given ram block and commence serialisation. -- elsif XMIT_RAM_SEQ = "110" then if XMIT_RAM_TYPE = '0' then XMIT_RAM_COUNT <= to_unsigned(128, 16); else XMIT_RAM_COUNT <= XMIT_TAPE_SIZE; end if; XMIT_RAM_CHKSUM_CNT <= to_unsigned(1, 2); XMIT_RAM_ADDR <= std_logic_vector(to_unsigned(0, 16)); XMIT_RAM_CHECKSUM <= std_logic_vector(to_unsigned(0, 16)); XMIT_RAM_STATE <= 1; XMIT_LOAD_2 <= '0'; -- If the DONE signal is low, then run the actual process, raising DONE when complete. elsif XMIT_RAM_DONE = '0' then -- Simple FSM to implement transmission of RAM contents according to MZ Tape Protocol. case(XMIT_RAM_STATE) is when 0 => when 1 => XMIT_RAM_BIT_CNT <= 8; -- 9 bits to transmit, pre 1 + 8 bits of data byte. XMIT_RAM_STATE <= 3; if XMIT_RAM_COUNT = 0 and XMIT_RAM_CHKSUM_CNT = 1 then XMIT_RAM_SR <= '1' & XMIT_RAM_CHECKSUM(15 downto 8); elsif XMIT_RAM_COUNT = 0 and XMIT_RAM_CHKSUM_CNT = 0 then XMIT_RAM_SR <= '1' & XMIT_RAM_CHECKSUM(7 downto 0); else -- Extract the size of the tape data block and the load address if this is the header. -- if XMIT_RAM_TYPE = '0' then if XMIT_RAM_ADDR = 18 then XMIT_TAPE_SIZE(7 downto 0) <= unsigned(HDR_RAM_DATAOUT); elsif XMIT_RAM_ADDR = 19 then XMIT_TAPE_SIZE(15 downto 8) <= unsigned(HDR_RAM_DATAOUT); -- If enabled, convert the filename to sharp ascii. elsif CONFIG(MZ_80C) = '1' and CONFIG(CMTASCII_OUT) = '1' and XMIT_RAM_ADDR >= std_logic_vector(to_unsigned(1, 9)) and XMIT_RAM_ADDR <= std_logic_vector(to_unsigned(17,9)) then XMIT_ASCII_RAM_ADDR <= '1' & HDR_RAM_DATAOUT; XMIT_RAM_STATE <= 2; end if; XMIT_RAM_SR <= '1' & HDR_RAM_DATAOUT; else XMIT_RAM_SR <= '1' & DATA_RAM_DATAOUT; end if; end if; -- Byte to be output is mapped via the Sharp Ascii <-> Ascii lookup table. when 2 => XMIT_RAM_SR <= '1' & ASCII_RAM_DATAOUT; XMIT_RAM_STATE <= 3; when 3 => XMIT_BIT_2 <= XMIT_RAM_SR(8); XMIT_LOAD_2 <= '1'; if XMIT_RAM_SR(8) = '1' and XMIT_RAM_BIT_CNT < 8 and XMIT_RAM_COUNT > 0 then XMIT_RAM_CHECKSUM <= XMIT_RAM_CHECKSUM + 1; end if; XMIT_RAM_SR <= XMIT_RAM_SR(7 downto 0) & '0'; XMIT_RAM_STATE <= 4; when 4 => -- As we are using the same clock freq, need to wait until XMIT_DONE is set to 0, indicating transmission in progress. if XMIT_LOAD_2 = '1' and XMIT_DONE = '0' then XMIT_LOAD_2 <= '0'; XMIT_RAM_STATE <= 5; end if; when 5 => -- Wait until the DONE signal is asserted before continuing. if XMIT_DONE = '1' then XMIT_RAM_STATE <= 6; end if; when 6 => XMIT_BIT_2 <= '0'; -- Reset bit.. if XMIT_RAM_BIT_CNT = 0 then if XMIT_RAM_COUNT = 0 and XMIT_RAM_CHKSUM_CNT = 0 then XMIT_RAM_STATE <= 7; else if XMIT_RAM_COUNT > 0 then XMIT_RAM_COUNT <= XMIT_RAM_COUNT - 1; XMIT_RAM_ADDR <= XMIT_RAM_ADDR + 1; elsif XMIT_RAM_COUNT = 0 and XMIT_RAM_CHKSUM_CNT > 0 then XMIT_RAM_CHKSUM_CNT <= XMIT_RAM_CHKSUM_CNT - 1; end if; XMIT_RAM_STATE <= 1; end if; else XMIT_RAM_BIT_CNT <= XMIT_RAM_BIT_CNT - 1; XMIT_RAM_STATE <= 3; end if; when others => XMIT_RAM_DONE <= '1'; end case; end if; end if; end if; end process; -- Process to send padding from CMT to MZ. -- -- This process transmits a set of pulses to represent the Gap, Tape Mark, Short Seperator or Long pulse of an MZ tape -- message. XMIT_PADDING_LOAD when high starts the generation, XMIT_PADDING_DONE is set high when generation completes. -- Normally, the invoker process sets up the number of bits and level in the parameters: -- XMIT_PADDING_CNT1 = Internal = If > 0, then transmit this number of bits first. -- XMIT PADDING_LEVEL1 = Internal = Level of the bit to transmit CNT1 times. -- XMIT PADDING_CNT2 = Internal = If > 0, then tramsit this number of bits second. -- XMIT_PADDING_LEVEL2 = Internal = Level of the bit to transmit CNT2 times. -- -- After completion of transmission, the Done signal is asserted high: -- XMIT_PADDING_DONE = Internal = 0 when transmission in progress, 1 when transmission completed. -- -- Clocks: -- CLKBUS(CKMASTER) = Internal = Base clock for encoding/decoding of pwm pulse. -- process( RST, CLKBUS(CKMASTER), XMIT_PADDING_LOAD ) begin if RST = '1' then XMIT_PADDING_DONE <= '1'; -- PADDING transmission complete signal, DONE = 1 when complete, 0 during transmit. XMIT_LOAD_1 <= '0'; -- LOAD signal to bit transmitted, loads required bit when = 1 for 1 cycle. PADDING_CNT1 <= 0; PADDING_LEVEL1 <= '0'; PADDING_CNT2 <= 0; PADDING_LEVEL2 <= '0'; PADDING_SEQ <= "000"; elsif CLKBUS(CKMASTER)'event and CLKBUS(CKMASTER) = '1' then if CLKBUS(CKENCPU) = '1' then -- Sample load signal and hold. Shift right 3 bits, msb = latest value. PADDING_SEQ(1 downto 0) <= PADDING_SEQ(2 downto 1); PADDING_SEQ(2) <= XMIT_PADDING_LOAD; -- If LOAD active for 3 periods, bring DONE low to acknowledge LOAD signal and start processing. -- if PADDING_SEQ = "111" then XMIT_PADDING_DONE <= '0'; end if; -- If LOAD active for 2 periods, sample and store the provided parameters. -- if PADDING_SEQ = "110" then -- Sample the parameters XMIT_PADDING_CNT1, XMIT_PADDING_CNT2, XMIT_PADDING_LEVEL1, XMIT_PADDING_LEVEL2 and -- write out the number of Level1 @ Cnt1, Level2 @ Cnt2 bits. PADDING_CNT1 <= XMIT_PADDING_CNT1; PADDING_LEVEL1 <= XMIT_PADDING_LEVEL1; PADDING_CNT2 <= XMIT_PADDING_CNT2; PADDING_LEVEL2 <= XMIT_PADDING_LEVEL2; XMIT_LOAD_1 <= '0'; end if; -- If DONE is low, we are processing. if XMIT_PADDING_DONE = '0' then -- Reset strobe when acknowledged by XMIT_DONE going low. if XMIT_LOAD_1 = '1' and XMIT_DONE = '0' then XMIT_LOAD_1 <= '0'; -- If we arent loading a padding sequence, then we are either waiting for a Done signal -- or need to commence a new transmission. -- elsif XMIT_LOAD_1 = '0' then -- If transmission buffer empty, setup next bit to transmit. -- if XMIT_DONE = '1' then -- Set the completion flag if the counters expire or PLAYING is disabled. --. if PLAYING = "000" or (PADDING_CNT1 = 0 and PADDING_CNT2 = 0) then -- Final wait for done on the last bit before setting our done flag. XMIT_PADDING_DONE <= '1'; -- First, transmit the nummber of Counter 1 bits defined in Level 1. elsif PADDING_CNT1 > 0 then XMIT_BIT_1 <= PADDING_LEVEL1; -- Set the mux input bit according to input level, XMIT_LOAD_1 <= '1'; -- Set the mux input to commence xmit. PADDING_CNT1 <= PADDING_CNT1 - 1; -- Decrement counter as this bit is now being transmitted. -- Then transmit the number of Counter 2 bits defined in Level 2. elsif PADDING_CNT2 > 0 then XMIT_BIT_1 <= PADDING_LEVEL2; XMIT_LOAD_1 <= '1'; PADDING_CNT2 <= PADDING_CNT2 - 1; end if; end if; end if; end if; end if; end if; end process; -- Process to write a bit (PWM encode) to the MZ input. -- The timings are as follows with a default SCLK of 2MHz. For faster operation, MZ clock is boosted -- and the SCLK is also boosted on a 1:1 relationship, thus the dividers are halved per boost. -- -- XMIT_LOAD_1 = FROM TAPE = When high, Bit available on XMIT_BIT_1 to encode and transmit to MZ. -- XMIT_LOAD_2 = FROM TAPE = When high, Bit available on XMIT_BIT_2 to encode and transmit to MZ. -- XMIT_DONE = TO TAPE = When high, transmission of bit complete. Resets to 0 on active XMIT_LOAD signal. -- WRITEBIT = FROM MZ = Encoded bit tranmitted to MZ. -- CLKBUS(CKMASTER)= = Base clock for encoding/decoding of pwm pulse. -- -- Machine Time uS Description N-80K(CKMEM) N-80K(CPU) N-700(CPU) N-700(CKMEM) -- MZ80KCA/700 464.00 Long Pulse Start 1856 928 1624 3248 -- 494.00 Long Pulse End 1976 988 1729 3458 -- 240.00 Short Pulse Start 960 480 840 1680 -- 264.00 Short Pulse End 1056 528 924 1848 -- 368.00 Read point. 1472 736 1288 2576 -- MZ80B 333.00 Long Pulse Start 2664 1332 -- 334.00 Long Pulse End 2672 1336 -- 166.75 Short Pulse Start 1334 667 -- 166.00 Short Pulse End 1328 664 -- 255.00 Read point. 2040 1020 -- process( RST, CLKBUS(CKMASTER), XMIT_LOAD_1, XMIT_LOAD_2 ) begin -- When RESET is high, hold in reset mode. if RST = '1' then XMIT_DONE <= '1'; -- Completion signal, 0 when transmitting, 1 when done. WRITEBIT <= '0'; -- Bit facing towards MZ input. XMIT_LIMIT <= 0; -- End of pulse. XMIT_COUNT <= 0; -- Pulse start, bit set to 1, reset to 0 on counter = 0 XMIT_SEQ <= "000"; elsif CLKBUS(CKMASTER)'event and CLKBUS(CKMASTER)='1' then if CLKBUS(CKENCPU) = '1' then -- Sample load signal and hold. Shift right 3 bits, msb = latest value. XMIT_SEQ(1 downto 0) <= XMIT_SEQ(2 downto 1); XMIT_SEQ(2) <= XMIT_LOAD_1 or XMIT_LOAD_2; -- If load is stable, acknowledge by bringing DONE low and start process. if XMIT_SEQ = "111" then XMIT_DONE <= '0'; WRITEBIT <= '1'; -- Store run values on 2nd clock cycle of LOAD being active. elsif XMIT_SEQ = "110" then -- Pulse periods for MZ80C type machines if CONFIG(MZ_KC) = '1' or CONFIG(MZ_A) = '1' then if (XMIT_LOAD_1 = '1' and XMIT_BIT_1 = '1') or (XMIT_LOAD_2 = '1' and XMIT_BIT_2 = '1') then XMIT_LIMIT <= 988; -- 1976; XMIT_COUNT <= -928; -- -1856; else XMIT_LIMIT <= 528; -- 1056; XMIT_COUNT <= -480; -- -960; end if; elsif CONFIG(MZ700) = '1' then -- Pulse periods for MZ700 type machines if (XMIT_LOAD_1 = '1' and XMIT_BIT_1 = '1') or (XMIT_LOAD_2 = '1' and XMIT_BIT_2 = '1') then XMIT_LIMIT <= 1729; -- 3458; XMIT_COUNT <= -1624; -- -3248; else XMIT_LIMIT <= 924; -- 1848; XMIT_COUNT <= -840; -- -1680; end if; else -- Pulse periods for MZ80B type machines if (XMIT_LOAD_1 = '1' and XMIT_BIT_1 = '1') or (XMIT_LOAD_2 = '1' and XMIT_BIT_2 = '1') then XMIT_LIMIT <= 1336; -- 2672; XMIT_COUNT <= -1332; -- -2664; else XMIT_LIMIT <= 664; -- 1328; XMIT_COUNT <= -667; -- -1334; end if; end if; -- On expiration of timer, signal completion. elsif XMIT_COUNT = XMIT_LIMIT then XMIT_DONE <= '1'; -- If the counter is running, format the output pulse. elsif XMIT_COUNT /= XMIT_LIMIT then -- At zero, we have elapsed the correct high period for the write bit, now bring it low for the remaining period. if XMIT_COUNT = 0 then WRITEBIT <= '0'; end if; XMIT_COUNT <= XMIT_COUNT + 1; end if; end if; end if; end process; -- Only enable debugging LEDS if enabled in the config package. -- DEBUGCMT: if DEBUG_ENABLE = 1 generate DEBUG_STATUS_LEDS(0) <= WRITEBIT; DEBUG_STATUS_LEDS(1) <= XMIT_DONE; DEBUG_STATUS_LEDS(2) <= XMIT_LOAD_1; DEBUG_STATUS_LEDS(3) <= XMIT_LOAD_2; DEBUG_STATUS_LEDS(4) <= XMIT_PADDING_LOAD; DEBUG_STATUS_LEDS(5) <= XMIT_PADDING_DONE; DEBUG_STATUS_LEDS(6) <= XMIT_RAM_LOAD; DEBUG_STATUS_LEDS(7) <= XMIT_RAM_DONE; DEBUG_STATUS_LEDS(8) <= PLAY_READY; DEBUG_STATUS_LEDS(9) <= PLAY_READY_CLR; DEBUG_STATUS_LEDS(10) <= PLAYING(2); DEBUG_STATUS_LEDS(11) <= PLAYING(1); DEBUG_STATUS_LEDS(12) <= PLAYING(0); DEBUG_STATUS_LEDS(13) <= '0'; DEBUG_STATUS_LEDS(14) <= '0'; DEBUG_STATUS_LEDS(15) <= RECORDING; DEBUG_STATUS_LEDS(16) <= READBIT; DEBUG_STATUS_LEDS(17) <= RCV_ERROR; DEBUG_STATUS_LEDS(18) <= '0'; DEBUG_STATUS_LEDS(19) <= RCV_CLR; DEBUG_STATUS_LEDS(20) <= RCV_DONE; DEBUG_STATUS_LEDS(21) <= RCV_LOAD; DEBUG_STATUS_LEDS(22) <= RCV_BYTE_CLR; DEBUG_STATUS_LEDS(23) <= RCV_BYTE_AVAIL; DEBUG_STATUS_LEDS(24) <= CMT_BUS_IN(pkgs.mctrl_pkg.STOP); DEBUG_STATUS_LEDS(25) <= CMT_BUS_IN(pkgs.mctrl_pkg.PLAY); DEBUG_STATUS_LEDS(26) <= CMT_BUS_IN(pkgs.mctrl_pkg.SEEK); DEBUG_STATUS_LEDS(27) <= CMT_BUS_IN(pkgs.mctrl_pkg.DIRECTION); DEBUG_STATUS_LEDS(28) <= CMT_BUS_IN(pkgs.mctrl_pkg.EJECT); DEBUG_STATUS_LEDS(29) <= CMT_BUS_IN(pkgs.mctrl_pkg.WRITEENABLE); DEBUG_STATUS_LEDS(31 downto 30) <= CONFIG(BUTTONS); end generate; DEBUGCMT1: if DEBUG_ENABLE = 0 generate DEBUG_STATUS_LEDS <= (others => '0'); end generate; end RTL;