OpenCores
URL https://opencores.org/ocsvn/funbase_ip_library/funbase_ip_library/trunk

Subversion Repositories funbase_ip_library

[/] [funbase_ip_library/] [trunk/] [TUT/] [ip.hwp.interface/] [eth_lan91c111_ctrl/] [1.0/] [vhd/] [lan91c111_init_module.vhd] - Rev 145

Compare with Previous | Blame | View Log

-------------------------------------------------------------------------------
-- Title      : Initialization Module for LAN91C111
-- Project    : 
-------------------------------------------------------------------------------
-- File       : Originally: DM9kA_init_module.vhd
-- Author     : Antti Alhonen (Original (simple) DM9000A version by Jussi Nieminen)
-- Company    : DCS/TUT
-- Last update: 2011-11-08
-- Platform   : 
-------------------------------------------------------------------------------
-- Description: Initializes LAN91C111. Half of the registers on the very same
-- integrated chip are separated from the others for some mystical reason* and are
-- accessed by EMULATING A SERIAL INTERFACE by issuing the serial data signals,
-- including clock timing, through the registers of the other half. YES, you
-- read it correctly, it really is true. 
-- Furthermore, some of the configuration options are located in both sections
-- and you have to manually make sure they match in the correct way; or, some of
-- them are fakes and only the ones in the right section really work. In addition,
-- user has to copy information between the sections. Etc.
 
-- (*) (yes I know why they did that but that's not an excuse;
-- they probably took two finished designs for MAC & PHY and made a quick
-- hack to connect them together in a way that somehow works but is hell to
-- use; it would be easier to use separate chips. Then, this chip is manufactured
-- for years and years to come. A decent replacement is not made.
 
-- The design also seems to suffer from some legacy from the ISA bus era,
-- despite the fact that the chip is NOT (specifically) meant for ISA busses; and
-- AFAIK is introduced years after the ISA went obsolete. Quite the opposite,
-- this is marketed as a chip for direct use with an embedded processor etc.
 
-- It probably would have been too easy to:
-- - Put all the configuration registers behind one unified interface (OMG),
-- - ONCE THEY HAVE THE 15-BIT ADDRESS BUS (for some mystical reason), really use *gasp*
--   FIVE bits (instead of the current three), thus eliminating the dumb concept of "IO
--   BANKS" (which, despite the name, has nothing to do with "IO"s). 
-- Oh, the DM9000A guys did it like that... The specifications may be in poor English
-- for that chip but it's not completely ****** up.
--
-- Congratulations for choosing LAN91C111 :-)! Good luck and have fun!
-------------------------------------------------------------------------------
-- Revisions  :
-- Date        Version  Author  Description
-- 2009/08/24  1.0      niemin95        Created
-------------------------------------------------------------------------------
 
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
 
-- constants
use work.lan91c111_ctrl_pkg.all;
 
 
entity lan91c111_init_module is
 
  generic (
    enable_tx_g : std_logic := '1';
    enable_rx_g : std_logic := '1');
 
  port (
    clk                     : in  std_logic;
    rst_n                   : in  std_logic;
    ready_out               : out std_logic;
    reg_addr_out            : out std_logic_vector( real_addr_width_c-1 downto 0 );
    config_data_out         : out std_logic_vector( lan91_data_width_c-1 downto 0 );
    nBE_out                 : out std_logic_vector( 3 downto 0 );
    read_not_write_out      : out std_logic;
    config_valid_out        : out std_logic;
    data_from_comm_in       : in  std_logic_vector( lan91_data_width_c-1 downto 0 );
    data_from_comm_valid_in : in  std_logic;
    comm_busy_in            : in  std_logic
    );
 
end lan91c111_init_module;
 
 
architecture rtl of lan91c111_init_module is
 
  type init_table_type is record
    phy   : std_logic;                  -- If '1', communicate with PHY registers
                                        -- instead of MAC. Addr will be 5 bits
                                        -- instead of 3, value will be 16 bits
                                        -- instead of 32 and nBE will be ignored.
    addr  : std_logic_vector( 4 downto 0 );  -- for normal MAC operations, use
                                             -- only 2 downto 0.
    value : std_logic_vector( lan91_data_width_c-1 downto 0 );
    nBE   : std_logic_vector( 3 downto 0 );
    writing : std_logic;
    sleep_time : integer range 0 to max_sleep_c;
    only_sleep : std_logic;  -- don't do anything but sleep.
 
    poll_until : std_logic;  -- use when writing='0'; if set to '1', don't advance to next step until 
    poll_bit_num : integer;  -- poll_bit_num bit of the read value equals to poll_value. Sleeping will
    poll_value : std_logic;  -- be done on every poll cycle, also after a match.
 
    copy       : std_logic;  -- When writing='1', use copy = '1' to write LAST READ VALUE (from
                             -- the SAME section (phy/mac)) instead of the "value" field; IN ADDITION,
                             -- if "poll_until" is '1', take the copy_bit_num'th bit FROM THE OTHER SECTION
    copy_bit_num: integer;   -- and put it in the poll_bit_num'th place. So: copy one bit from the PHY index x to
                             -- MAC index y; read MAC, READ PHY, write MAC with copy = 1, poll_until = 1,
                             -- copy_bit_num = x and poll_bit_num = y. (So this is not a poll, just reusing fields.
                             -- Poll happens only when writing = '0'.)
  end record;
 
  constant init_values_c : integer := 28;
  type init_table_array is array (0 to init_values_c-1) of init_table_type;
 
  -- Remember: manually change the bank to 3 before entering any PHY commands.
  constant init_table_c : init_table_array := (
--   PHY    ADDR     DATA                               nBE   W?  Sleep   Sleep_only POLL, POLLBIT, POLLVAL, COPY, COPYBIT
    ('0', "00000", x"00000000",                       "0000", '1', clk_hz_c/20, '1', '0',    0,       '0',    '0',   0),  -- Sleep 50 ms
    ('0', "00111", x"00000000",                       "1100", '1', 0,           '0', '0',    0,       '0',    '0',   0),  -- Select BANK 0
    ('0', "00010", x"00008000",                       "1100", '1', 0,           '0', '0',    0,       '0',    '0',   0),  -- Soft reset
    ('0', "00010", x"00000000",                       "1100", '1', clk_hz_c/20, '0', '0',    0,       '0',    '0',   0),  -- Clear soft reset and sleep 50 ms.
    ('0', "00000", x"000000" & "0000000" & enable_tx_g,"1100", '1', 0,           '0', '0',    0,       '0',    '0',   0),  -- TX Enable according to enable_tx_g. Padding doesn't work, don't bother
    ('0', "00101", x"0000" & "00111000" & "00010000", "1100", '1', 0,           '0', '0',    0,       '0',    '0',   0),  -- Autonegotiation on, leds.
-- Reset the PHY to start autonegotiation.
    ('0', "00111", x"00000003",                       "1100", '1', 0,           '0', '0',    0,       '0',    '0',   0),  -- Select BANK 3
    ('1', "00000", x"00008000",                       "1100", '1', clk_hz_c/20, '0', '0',    0,       '0',    '0',   0),  -- Reset PHY, sleep 50 ms.
    ('1', "00000", x"0000" & "0011000100000000",      "1100", '1', clk_hz_c*2,  '0', '0',    0,       '0',    '0',   0),  -- PHY isolation mode off, ANEG on. Sleep 2 sec.
--    ('1', "00010", x"00000000",                       "1100", '0', 50          ,'0', '0',    0,       '0',    '0',   0),  -- DEBUG: Read reg 2 from PHY, company ID.
    ('1', "00001", x"00000000",                       "1100", '0', clk_hz_c/200,'0', '1',    5,       '1',    '0',   0),  -- Poll for ANEG_ACK in PHY for every 5 ms until '1'.
    ('1', "00001", x"00000000",                       "1100", '0', clk_hz_c/200,'0', '1',    2,       '1',    '0',   0),  -- Poll for LINK in PHY for every 5 ms until '1'.
-- Autonegotiation is done.
-- Now, copy one PHY register bit to appropriate location in the MAC register.
    ('0', "00111", x"00000000",                       "1100", '1', 0,           '0', '0',    0,       '0',    '0',   0),  -- Select BANK 0
    ('0', "00000", x"00000000",                       "1100", '0', 0,           '0', '0',    0,       '0',    '0',   0),  -- Read BANK 0 offset 0 to get a local copy here (MAC).
    ('0', "00111", x"00000003",                       "1100", '1', 0,           '0', '0',    0,       '0',    '0',   0),  -- Select BANK 3 to be able to access PHY.
    ('1', "10010", x"00000000",                       "1100", '0', 0,           '0', '0',    0,       '0',    '0',   0),  -- Read PHY register 18 to get a local copy here (PHY).
    ('0', "00111", x"00000000",                       "1100", '1', 0,           '0', '0',    0,       '0',    '0',   0),  -- Select BANK 0
    ('0', "00000", x"ACDCABBA",                       "1100", '1', 0,           '0', '1',   15,       '0',    '1',   6),  -- Copy bit 6 from PHY we read earlier to MAC offset 0 bit 15.
 
-- Now, configure all MAC registers:
    ('0', "00111", x"00000000",                       "1100", '1', 0,           '0', '0',    0,       '0',    '0',   0),  -- Select BANK 0
    ('0', "00010", x"0000" & "0000001" & enable_rx_g & x"00", "1100", '1', 0,   '0', '0',    0,       '0',    '0',   0),  -- STRIP CRC = on, RX enable according to enable_rx_g.
    ('0', "00111", x"00000001",                       "1100", '1', 0,           '0', '0',    0,       '0',    '0',   0),  -- Select BANK 1
    ('0', "00010", MAC_addr_c(23 downto 16) & MAC_addr_c(31 downto 24) & MAC_addr_c(39 downto 32) & MAC_addr_c(47 downto 40), "0000",'1',0,'0','0',0,'0','0',0), -- MAC Address 32 LSb's.
    ('0', "00100", x"0000" & MAC_addr_c(7 downto 0) & MAC_addr_c(15 downto 8),"1100",'1',0,'0','0',0, '0',    '0',   0),  -- MAC Address 16 MSb's.
    ('0', "00110", x"0000" & "0001101000010000",      "1100", '1', 0,           '0', '0',    0,       '0',    '0',   0),  -- AUTO_RELEASE on.
    ('0', "00111", x"00000002",                       "1100", '1', 0,           '0', '0',    0,       '0',    '0',   0),  -- Select BANK 2
    ('0', "00000", x"000000" & "01000000",            "1100", '1', 0,           '0', '0',    0,       '0',    '0',   0),  -- Issue MMU Reset Command, just to be sure.
    ('0', "00110", x"0000" & "0000000" & enable_rx_g & x"FF", "1100", '1', 0,           '0', '0',    0,       '0',    '0',   0),  -- RCV interrupt mask according to enable_rx_g. Also ack all.
-- Alles in Ordnung, and link should be up. Let's sleep for a few seconds so that the other side is up, too.
    ('0', "00000", x"ACDCABBA",                       "1111", '1', clk_hz_c*3,  '1', '0',    0,       '0',    '0',   0),   -- Sleep only, three seconds.
--    ('0', "00110", x"00000000",                       "1100", '0', 50          ,'0', '0',    0,       '0',    '0',   0),  -- DEBUG: Read that interrupt mask register.
    ('0', "00000", x"ACDCABBA",                       "1111", '1', clk_hz_c*3,  '1', '0',    0,       '0',    '0',   0)    -- Sleep only, three seconds.
    );                                  -- And note, we left in BANK 2, as we should!! Other modules expect this.
 
 
  signal init_cnt_r : integer range 0 to init_values_c - 1;
  signal ready_r : std_logic;
  signal data_from_comm_r : std_logic_vector( lan91_data_width_c-1 downto 0 );
 
  type init_state_type is (start, read_data, wait_busy, sleep, phy_horror, finished);
  signal state_r : init_state_type;
 
  type phy_horror_state_type is (start_horror,
                                 write1, wait1, write2, wait2, write3, wait3,
                                 read3, waitread  -- they come after wait2, only if reading
                                 );
 
  signal phy_horror_state_r : phy_horror_state_type;
 
  constant phy_horror_length_c : integer := 64;  -- PHY configuration horror
                                                 -- cycle lasts for 64 serial
                                                 -- clock cycles, including the
                                                 -- "IDLE" period that could be
                                                 -- called a START CONDITION
                                                 -- as it is a condition for
                                                 -- a successful start.
 
  signal phy_horror_counter_r : integer range 0 to phy_horror_length_c;
  -- let's use 250 ns waiting time between clk cycles. There is some extra
  -- cycles in addition to this.
  constant phy_horror_wait_time_c : integer := clk_hz_c/4000000;  
  signal phy_horror_wait_cnt_r : integer range 0 to phy_horror_wait_time_c;
  -- Phy horror register includes first (leftmost) 34 bits hard-wired to the start condition.
  -- Rightmost bits are registers. Leftmosts (MSb's) are send first. Register
  -- is set before the sending of the serial bits starts.
  signal phy_horror_register_r : std_logic_vector(phy_horror_length_c-1 downto 0);
  signal phy_horror_interface_MDOE_r : std_logic;  -- '0' if we want to instruct the Phy horror interface to hi-Z state.
  signal phy_horror_read_data_flowing_r : std_logic;  -- Comes high two Horror Cycles after
                                                      -- MDOE_r goes low.
 
  signal horror_read_r : std_logic_vector(15 downto 0);
 
  signal reset_sleep_cnt_r : integer range 0 to reset_sleep_c;
  signal sleep_cnt_r : integer range 0 to max_sleep_c;
 
  -- 1 second with 25MHz (yes, it's really necessary)
  constant link_wait_time_c : integer := 25000000;
 
 
 
-------------------------------------------------------------------------------
begin  -- rtl
-------------------------------------------------------------------------------
 
  assert phy_horror_wait_time_c > 0 report "this has to be 1 or more" severity failure;
 
  ready_out <= ready_r;
 
  -- Concurrent part of the phy_horror_register: the start condition consisting
  -- of things called "IDLE" and "START BITS".
  phy_horror_register_r(63 downto 30) <= "1111111111111111111111111111111101";
 
  init: process (clk, rst_n)
  begin  -- process init
    if rst_n = '0' then                 -- asynchronous reset (active low)
 
      ready_r           <= '0';
      init_cnt_r        <= 0;
      data_from_comm_r  <= (others => '0');
      reset_sleep_cnt_r <= 0;
      reg_addr_out      <= (others => '0');
      config_data_out   <= (others => '0');
      read_not_write_out <= '0';
      config_valid_out  <= '0';
      state_r           <= start;
      phy_horror_state_r <= start_horror;
 
      horror_read_r <= (others => '0');
      sleep_cnt_r <= 0;
 
 
    elsif clk'event and clk = '1' then  -- rising clock edge
 
      -- DEFAULTS:
      config_valid_out <= '0';
 
      if reset_sleep_cnt_r /= reset_sleep_c then
        -- sleep for a while after reset release
        reset_sleep_cnt_r <= reset_sleep_cnt_r + 1;
 
      elsif ready_r = '0' then
 
        case state_r is
          when start =>
            if init_table_c(init_cnt_r).only_sleep = '1' then
              sleep_cnt_r <= init_table_c(init_cnt_r).sleep_time;
              state_r <= sleep;
            elsif init_table_c(init_cnt_r).phy = '1' then
              -- Enter the psycho mode. 
              state_r <= phy_horror;
            else
              -- A _*NORMAL*_ OPERATION (at least quite normal)!
              reg_addr_out <= init_table_c( init_cnt_r ).addr(real_addr_width_c-1 downto 0);
              if init_table_c( init_cnt_r ).copy = '1' then
                config_data_out <= data_from_comm_r;
                if init_table_c(init_cnt_r).poll_until = '1' then  -- this means the "modify" command.
                  config_data_out(init_table_c(init_cnt_r).poll_bit_num) <= horror_read_r(init_table_c(init_cnt_r).copy_bit_num);
                  -- bypass this one bit.
                end if;
              else
                config_data_out <= init_table_c( init_cnt_r ).value;
              end if;
              read_not_write_out <= not init_table_c( init_cnt_r ).writing;
              nBE_out <= init_table_c( init_cnt_r ).nBE;
              config_valid_out <= '1';
 
              -- change state once busy is up (comm is working)
              if comm_busy_in = '1' then
                if init_table_c( init_cnt_r ).writing = '0' then
                  state_r <= read_data;
                else
                  state_r <= wait_busy;
                end if;
              end if;
            end if;
 
          when read_data =>
 
            -- reading is quite useless at the moment, but for example some
            -- registers can be cleared by reading if necessary
            if data_from_comm_valid_in = '1' then
              data_from_comm_r <= data_from_comm_in;
              state_r <= wait_busy;
            end if;
 
          when wait_busy =>
            if comm_busy_in = '0' then
              sleep_cnt_r <= init_table_c(init_cnt_r).sleep_time;
              state_r <= sleep;
            end if;
 
          when sleep =>
            if sleep_cnt_r /= 0 then
              sleep_cnt_r <= sleep_cnt_r - 1;
            else
              -- Were we polling something?
              if init_table_c(init_cnt_r).poll_until = '1' and
                 ((init_table_c(init_cnt_r).phy = '1' and horror_read_r(init_table_c(init_cnt_r).poll_bit_num) /= init_table_c(init_cnt_r).poll_value) or
                 (init_table_c(init_cnt_r).phy = '0' and data_from_comm_r(init_table_c(init_cnt_r).poll_bit_num) /= init_table_c(init_cnt_r).poll_value))
              then
                state_r <= start;       -- back to start to poll again!
              else
                -- We were not polling, or got what we wanted.
                if init_cnt_r = init_values_c-1 then
                  state_r <= finished;
                else
                  init_cnt_r <= init_cnt_r + 1;  -- to next row.
                  state_r <= start;
                end if;
              end if;
            end if;
 
          when phy_horror =>
            case phy_horror_state_r is
              when start_horror =>
                phy_horror_register_r(29 downto 0) <=
                  not init_table_c(init_cnt_r).writing &
                  init_table_c(init_cnt_r).writing &
                  "00000" &
                  init_table_c(init_cnt_r).addr(4 downto 0) &
                  "10" &                -- "turnaround" is written always
                                        -- as "10". Request for high-impedance
                                        -- is done separately.
                  init_table_c(init_cnt_r).value(15 downto 0);
 
                if init_table_c( init_cnt_r ).copy = '1' then
                  phy_horror_register_r(15 downto 0) <= horror_read_r;  -- bypass the value.
                  if init_table_c(init_cnt_r).poll_until = '1' then  -- this means the "modify" command.
                    phy_horror_register_r(init_table_c(init_cnt_r).poll_bit_num) <= data_from_comm_r(init_table_c(init_cnt_r).copy_bit_num);
                    -- bypass this one bit.
                  end if;
                end if;
 
                phy_horror_counter_r <= phy_horror_length_c-1;
                -- Everything will be 16 bit writes to the same stupid
                -- "Management Interface" register.
                reg_addr_out <= "100";
                nBE_out <= "1100";
                phy_horror_interface_MDOE_r <= '1';
                phy_horror_read_data_flowing_r <= '0';
                phy_horror_state_r <= write1;
 
              when write1 =>
                if comm_busy_in = '0' then
                  read_not_write_out <= '0';
                  config_data_out <= x"0000" & "00110011" & "0011"
                                     & phy_horror_interface_MDOE_r
                                     & '0'  -- MCLK
                                     & '0'  -- MDI
                                     & phy_horror_register_r(phy_horror_counter_r);
                  config_valid_out <= '1';
                  phy_horror_state_r <= wait1;
                  phy_horror_wait_cnt_r <= phy_horror_wait_time_c;
                end if;
 
              when wait1 =>
                if comm_busy_in = '0' then
                  if phy_horror_wait_cnt_r = 0 then
                    if init_table_c(init_cnt_r).writing = '0' and phy_horror_read_data_flowing_r = '1' then
                      phy_horror_state_r <= read3;
                    else
                      phy_horror_state_r <= write2;
                    end if;
                  else
                    phy_horror_wait_cnt_r <= phy_horror_wait_cnt_r - 1;
                  end if;
                end if;
 
              when write2 =>
                if comm_busy_in = '0' then
                  read_not_write_out <= '0';
                  config_data_out <= x"0000" & "00110011" & "0011"
                                     & phy_horror_interface_MDOE_r
                                     & '1'  -- OOOOOOOHHH, create the clock edge!!!!
                                     & '0'  -- MDI
                                     & phy_horror_register_r(phy_horror_counter_r);
                  config_valid_out <= '1';
                  phy_horror_state_r <= wait2;
                  phy_horror_wait_cnt_r <= phy_horror_wait_time_c;
                end if;
 
              when wait2 =>
                if comm_busy_in = '0' then
                  if phy_horror_wait_cnt_r = 0 then
                    phy_horror_state_r <= write3;
                  else
                    phy_horror_wait_cnt_r <= phy_horror_wait_cnt_r - 1;
                  end if;
                end if;
 
              when read3 =>
                -- This state is skipped when writing.
                if comm_busy_in = '0' then
                  read_not_write_out <= '1';  -- read operation to the same addr
                  config_valid_out <= '1';
                  phy_horror_state_r <= waitread;
                end if;
 
              when waitread =>
                -- This state is skipped when writing.
                if data_from_comm_valid_in = '1' then
                  -- data valid means also not busy so no need to check that.
                  -- Just read the data we are interested in here
                  horror_read_r(phy_horror_counter_r) <= data_from_comm_in(1);
                  phy_horror_state_r <= write2;
                end if;
 
              when write3 =>
                if comm_busy_in = '0' then
                  read_not_write_out <= '0';
                  config_data_out <= x"0000" & "00110011" & "0011"
                                     & phy_horror_interface_MDOE_r
                                     & '0'  -- MCLK  Low again, OMG
                                     & '0'  -- MDI
                                     & phy_horror_register_r(phy_horror_counter_r);
                  config_valid_out <= '1';
                  phy_horror_state_r <= wait3;
                  phy_horror_wait_cnt_r <= phy_horror_wait_time_c;
                end if;
 
              when wait3 =>
                if comm_busy_in = '0' then
                  if phy_horror_wait_cnt_r = 0 then
                    if phy_horror_counter_r = 0 then
                      -- OH YES, we got all the bits done.
                      phy_horror_state_r <= start_horror;
                      state_r <= wait_busy;  -- This is good, away from the horror FSM.
                    else
                      phy_horror_counter_r <= phy_horror_counter_r - 1;
                      phy_horror_state_r <= write1;  -- To the next bit...
                      if init_table_c(init_cnt_r).writing = '0' then
                        if phy_horror_counter_r = 18 then
                          -- Turnaround time.
                          phy_horror_interface_MDOE_r <= '0';
                        elsif phy_horror_counter_r = 16 then
                          -- Valid data is coming next (hopefully)
                          phy_horror_read_data_flowing_r <= '1';
                        end if;
                      end if;
                    end if;
                  else
                    phy_horror_wait_cnt_r <= phy_horror_wait_cnt_r - 1;
                  end if;
                end if;
 
 
              when others => null;
            end case;
 
          when finished =>
 
            ready_r <= '1';
 
          when others => null;
        end case;
 
      else
        -- ready_r = '1'
 
        reg_addr_out       <= (others => '0');
        config_data_out    <= (others => '0');
        read_not_write_out <= '0';
        config_valid_out   <= '0';
 
      end if;
    end if;
  end process init;
 
 
end rtl;
 

Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.