*

mfm_encoder.vhd


--------------------------------------------------------------------------

-- FILENAME : mfm_encoder.vhd
--
-- Takes the parallel contents of the RAM and converts it to serial. The
-- serial output from the RAM is then converted to an MFM (Modified
-- Frequency Modulation) signal. The MFM signal is output at a rate of
-- 500KHz so that it can be fed directly into the disk drive.
--
-- Before the data is output this component first outputs a series of 10
-- zero valued bytes followed by the 4 byte address mark of the data
-- field. The data is then followed by the CRC and then a series of 1's.
-- In other words this component outputs an entire data field. The
-- reasons for this are described in the final report.
--
-- To start this component assert EN.
--
-- AUTHOR : Craig Dunn
-- DATE STARTED : 24 February 2004
-- TAB SETTING : 4
-- RESET : Synch via MCLK (de-assert EN)
-- CLOCK : 32MHz (clock dependent)
-- KNOWN BUGS : None
-- VERSION : 1.0
--
-- All of the design and code in this module is my own work. No design or
-- code has been borrowed or copied from any source.
--------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity mfm_encoder is
port ( EN : in std_logic;
MCLK : in std_logic;
RAM_DO : in std_logic_vector(7 downto 0);
CRC_DATA : out std_logic_vector(7 downto 0);
CRC_IN : out std_logic_vector(15 downto 0);
CRC_OUT : in std_logic_vector(15 downto 0);
RAM_CLK : out std_logic;
RAM_ADDR : out std_logic_vector(8 downto 0);
BSY : out std_logic;
MFM_OUT : out std_logic
);
end mfm_encoder;

architecture mfm_encoder_arch of mfm_encoder is

-- types
type shifter_type is ( INIT_CLK_LOW, INIT_CLK_HIGH, OUTPUT_GAP, OUTPUT_AM, OUTPUT_FB, IDLE, WHILE_500KHZ_LOW, WHILE_500KHZ_HIGH, OUTPUT_BIT, INC_ADDR, CLOCK_RAM, UPDATE_CRC, OUTPUT_CRC );
type write_type is ( ADDRESS_MARK, GAP, FB, DATA, CRC_FIELD );

-- generate_500khz
signal clk500khz : std_logic;
signal count : integer range 0 to 63;

-- shifter
signal write_state : write_type;
signal shifter_state : shifter_type;
signal serial_output : std_logic_vector(1 downto 0);
signal serial_addr : std_logic_vector(8 downto 0);
signal bit_ptr : integer range 7 downto 0;
signal crc_ptr : integer range 23 downto 0;
signal edge_count : integer range 0 to 176;
signal byte_count : integer range 0 to 3;

-- crc
signal sig_crc_out : std_logic_vector(23 downto 0);

-- pulser
signal pulse_duration : integer range 0 to 12;

-- constants
constant PULSE_LENGTH : integer := 12;
constant BIT_PERIOD : integer := 64;
constant AM_BYTE_CNT : integer := 3;
constant GAP_EDGES : integer := 88;
constant DF_AM_BYTE : std_logic_vector(7 downto 0) := x"FB";
constant DF_AM_CRC : std_logic_vector(15 downto 0) := x"CDB4";

begin

--------------------------------------------------------------------------
--------------------------------------------------------------------------

sig_crc_out(23 downto 8) <= CRC_OUT;

RAM_ADDR <= serial_addr;


--------------------------------------------------------------------------
--------------------------------------------------------------------------

MFM_OUT <= '1' when pulse_duration = 0 else '0';

pulser : process(MCLK)
begin
if rising_edge(MCLK) then
if EN = '0' then
pulse_duration <= 0;
else
-- If the current bit is 1 then generate a pulse in the middle of the
-- bit period. Or if the current and next bit are both zero generate
-- a pulse at the end of the bit period.
if (serial_output(1) = '1' and count = ((BIT_PERIOD PULSE_LENGTH) / 2)) or
(serial_output = "00" and count = (BIT_PERIOD (PULSE_LENGTH / 2))) or
(pulse_duration > 0) then

if pulse_duration = PULSE_LENGTH then
pulse_duration <= 0;
else
pulse_duration <= pulse_duration + 1;
end if;

end if;
end if;
end if;
end process pulser;


--------------------------------------------------------------------------
-- PROCESS : generate_500khz
--
-- Generate a 500KHz clock from MCLK. The 500KHz clock is used to
-- generate the MFM signal.
--
-- NOTE : Rather than counting upto half the bit period then inverting
-- the 500KHz clock this process counts upto twice the bit period.
-- This is because the second half of the bit period is used by
-- the pulser process.
--------------------------------------------------------------------------

generate_500khz : process(MCLK)
begin
if rising_edge(MCLK) then
if EN = '0' then
count <= 0;
clk500khz <= '1';
elsif count = ((BIT_PERIOD / 2) 1) then
clk500khz <= not clk500khz;
count <= count + 1;
elsif count = (BIT_PERIOD 1) then
clk500khz <= not clk500khz;
count <= 0;
else
count <= count + 1;
end if;
end if;
end process generate_500khz;


--------------------------------------------------------------------------
-- PROCESS : shifter
--
-- Take the parallel contents of the RAM and convert it to serial.
--
-- This FSM does it's work on the rising edge of the 500KHz clock. The
-- MFM conversion is then done in the later on in the 500KHz period.
--------------------------------------------------------------------------

shifter : process(MCLK)
begin
if rising_edge(MCLK) then
if EN = '0' then
serial_addr <= (others => '0');
RAM_CLK <= '1';
shifter_state <= INIT_CLK_LOW;
BSY <= '1';
crc_in <= DF_AM_CRC;
crc_data <= DF_AM_BYTE;
crc_ptr <= 23;
edge_count <= 0;
byte_count <= 0;
write_state <= GAP;
serial_output <= "00";
bit_ptr <= 6;
sig_crc_out(7 downto 0) <= x"FF";
else
case shifter_state is

-- This is the first state of the FSM. This and the
-- next state are responsible for setting the RAM up
-- with the first byte to be output. The data on in
-- the RAM is output on the rising edge of the clock
-- and the address is set to zero when this FSM is
-- disabled.
---------------------------------------------------------------
when INIT_CLK_LOW =>
RAM_CLK <= '0';
shifter_state <= INIT_CLK_HIGH;

when INIT_CLK_HIGH =>
RAM_CLK <= '1';
shifter_state <= OUTPUT_GAP;


-- The data field begins with a gap of zero valued bytes
-- which are used by the DPLL as SYNC bytes. The number
-- of bytes is normally 12 but the exact number is not
-- important.
--
-- The serial output is set to zero when this FSM is disabled.
---------------------------------------------------------------
when OUTPUT_GAP =>
if edge_count = GAP_EDGES then
-- The first bit of the next byte begins with a 1.
serial_output <= "01";
edge_count <= 0;
write_state <= ADDRESS_MARK;
else
edge_count <= edge_count + 1;
end if;

shifter_state <= WHILE_500KHZ_HIGH;


-- Directly after the gap of zero's there are 3 bytes of
-- 0xA1 which make up the first 3 bytes of the 4 byte
-- address mark. The value 0xA1 contains an MFM encoding
-- violation, this is explained in the final report.
---------------------------------------------------------------
when OUTPUT_AM =>
case edge_count is
when 0 => serial_output <= "10";
when 1 => serial_output <= "01";
when 2 => serial_output <= "10";
when 3 => serial_output <= "00";
when 4 => serial_output <= "01";
when 5 => serial_output <= "00"; -- generate an MFM violation
when 6 => serial_output <= "01";
when 7 => serial_output <= "11";
when others =>
null;
end case;

if edge_count = 7 then
edge_count <= 0;
if byte_count = (AM_BYTE_CNT 1) then
write_state <= FB;
else
byte_count <= byte_count + 1;
end if;
else
edge_count <= edge_count + 1;
end if;

shifter_state <= WHILE_500KHZ_HIGH;


-- The final byte of the address mark is the value 0xFB.
-- Following the value of 0xFB is the data.
---------------------------------------------------------------
when OUTPUT_FB =>
case edge_count is
when 0 => serial_output <= "11";
when 1 => serial_output <= "11";
when 2 => serial_output <= "11";
when 3 => serial_output <= "11";
when 4 => serial_output <= "10";
when 5 => serial_output <= "01";
when 6 => serial_output <= "11";
when 7 => serial_output <= '1' & RAM_DO(7);
when others =>
null;
end case;


-- Once the whole byte has been output update the
-- CRC and start writing the data.
---------------------------------------------------------------
if edge_count = 7 then
crc_in <= sig_crc_out(23 downto 8);
crc_data <= RAM_DO;
write_state <= DATA;
else
edge_count <= edge_count + 1;
end if;

shifter_state <= WHILE_500KHZ_HIGH;


-- The entire contents of the RAM has been converted to serial
-- so now just stay here. When EN is deasserted this FSM will
-- reset.
---------------------------------------------------------------
when IDLE =>
shifter_state <= IDLE;


-- Stay in this state while the 500KHz clock is low. This
-- allows the MFM conversion to take place without being
-- interrupted by this FSM.
---------------------------------------------------------------
when WHILE_500KHZ_LOW =>
if clk500khz = '1' then
case write_state is
when GAP => shifter_state <= OUTPUT_GAP;
when ADDRESS_MARK => shifter_state <= OUTPUT_AM;
when FB => shifter_state <= OUTPUT_FB;
when DATA => shifter_state <= OUTPUT_BIT;
when CRC_FIELD => shifter_state <= OUTPUT_CRC;
when others =>
null;
end case;
else
shifter_state <= WHILE_500KHZ_LOW;
end if;


-- The serial output from this FSM happens in the first few
-- MCLK pulses of the 500KHz clock so wait here for the
-- remaing time.
---------------------------------------------------------------
when WHILE_500KHZ_HIGH =>
RAM_CLK <= '0';

if clk500khz = '0' then
shifter_state <= WHILE_500KHZ_LOW;
else
shifter_state <= WHILE_500KHZ_HIGH;
end if;


-- On the rising edge of the 500KHz clock output the
-- next bit from the RAM.
---------------------------------------------------------------
when OUTPUT_BIT =>
serial_output <= serial_output(0) & RAM_DO(bit_ptr);

-- If it's the final bit of the byte go and get the next byte.
if bit_ptr = 0 then
bit_ptr <= 7;
shifter_state <= INC_ADDR;
else
bit_ptr <= bit_ptr 1;
shifter_state <= WHILE_500KHZ_HIGH;
end if;


-- Increment the RAM address to get the next byte.
---------------------------------------------------------------
when INC_ADDR =>
if serial_addr = "111111111" then
-- We have now converted the entire contents of the RAM
-- so write the CRC field next.
write_state <= CRC_FIELD;
shifter_state <= WHILE_500KHZ_HIGH;
else
-- Increment the RAM address.
serial_addr <= serial_addr + '1';
shifter_state <= CLOCK_RAM;
end if;


-- When the RAM address is changed the RAM clock must be pulsed
-- and the CRC needs to updated with the new byte.
---------------------------------------------------------------
when CLOCK_RAM =>
RAM_CLK <= '1';
shifter_state <= UPDATE_CRC;


-- Directly after getting a new byte from the RAM update the
-- CRC.
---------------------------------------------------------------
when UPDATE_CRC =>
crc_in <= sig_crc_out(23 downto 8);
crc_data <= RAM_DO;
shifter_state <= WHILE_500KHZ_HIGH;



-- Now all the data has been outputted output the CRC.
---------------------------------------------------------------
when OUTPUT_CRC =>
serial_output <= serial_output(0) & sig_crc_out(crc_ptr);

-- Once the final bit has been output stay in the IDLE state.
if crc_ptr = 0 then
BSY <= '0';
shifter_state <= IDLE;
else
crc_ptr <= crc_ptr 1;
shifter_state <= WHILE_500KHZ_HIGH;
end if;


when others =>
null;


end case;
end if;
end if;
end process shifter;


--------------------------------------------------------------------------
--------------------------------------------------------------------------

end mfm_encoder_arch;


BackHome