file: hc11spi.vhd
--
-- Motorola HC11 VHDL model
-- Copyright (C) Green Mountain Computing Systems, 1995
-- All rights reserved.
--
-- This software is provided "as is" without warranty of any kind.  Green 
-- Mountain Computing Systems does not accept any responsibility for results
-- obtained by using this software and does not guarantee that the software
-- is correct.
--
-- hc11spi.vhd : This is the VHDL behavioral implementation of the HC11's
--        Serial Peripheral Interface (SPI)
--
-- 5/25/95 : Create - Scott Thibault
--

-- Uses vector functions and the HC11 types.
library vector,hc11type;
use vector.functions.all;
use hc11type.types.all;

entity spi is
  port (E : in bit;
        ph1, ph2, reset : in bit;
        ss: in bit;               -- External SS pin
        sckin: in bit;            -- SCK input for slave from master
        sckout: out bit;          -- SCK generated by master
        mi,si : in bit;           -- Slave's serial I/O
        mo,so : out bit;          -- Master's serial I/O
        ss_dir : in bit;          -- Direction of SS I/O pin
        mfault : out bit;         -- Active high, when mfault has occurred
        int_spi : out bit;        -- SPI's interrupt request line
        spe,slave : out bit;      -- SPI state used in external pin logic

        -- System bus signals
        reg_en : in bit;
        as, bus_rw : in bit;
        bus_addr : in word;
        bus_data : inout databus bus);

  -- Bit positions of various SPI related flags in HC11 control registers
  constant SPIE : integer:=7;
  constant SPE1 : integer:=6;
  constant DWOM : integer:=5;
  constant MSTR : integer:=4;
  constant CPOL : integer:=3;
  constant CPHA : integer:=2;
  constant SPR1 : integer:=1;
  constant SPR0 : integer:=0;
  constant SPIF : integer:=7;
  constant WCOL : integer:=6;
  constant MODF : integer:=4;

  -- States of a state machine that control the SPI
  constant WAIT_ST : integer:=0;
  constant TRAN_ST : integer:=1;
  constant RECV_ST : integer:=2;
  constant DELAY1_ST : integer:=3;
  constant DELAY2_ST : integer:=4;
  constant DELAY3_ST : integer:=5;
end spi;

architecture behavoir of spi is
  -- SPI registers
  signal SPCR,SPSR,SPDR_shift : byte;

  -- Control registers to set SPSR flags, and SPDR value read by the CPU
  signal SPDR_read: byte;
  signal set_SPIF,set_MODF : bit;    -- Controls signals for SPSR register
  signal SPDR_full : bit;            -- Flag set when the SPDR is loaded
  signal SPSR_read: byte;             --  Value of SPSR, when last read

  -- Clock divider block signals to generate different transfer rates
  signal divided : bit_vector (4 downto 0);
  signal prescaled : bit_vector (3 downto 0);

  -- Miscellaneous internal signals    
  signal clock,spi_clk,bit_in,bit_out,shift_edge,load : bit;
  signal state,no_bits,clk_sel : integer:=0;
  signal found_start,shift: boolean;

  -- Bus signal values latched during valid address portion of bus cycle
  signal address : natural;
  signal rw,wr_enable,rd_enable : bit;
begin
  sckout<=clock;        -- Master's clock drives the sck output
  mfault<=SPSR(MODF);        -- Single mode faults to pin buffers

  -- The spe and slave signals make the current mode of the SPI available to
  --   other parts of the HC11 model.
  spe<=SPCR(SPE1);
  slave<=not SPCR(MSTR);

  -- Inputs and outputs are reversed for master and slave
  bit_in<=mi when SPCR(MSTR)='1' else si;  -- Select correct input

  process (bit_out,SPCR)                   -- Select correct output
  begin
    if (SPCR(MSTR)='1') then
      mo<=bit_out;
    else
      so<=bit_out;
    end if;
  end process;

  -- Generate interrupt, if enabled, when a trasfer is complete (SPIF='1')
  int_spi<=(SPSR(SPIF) or SPSR(MODF)) and SPCR(SPIE);

  -- Generate divided signals from ph2
  divider: process (ph2)
  begin
    if (ph2='1' and ph2'event) then
      divided<=divided+"00001";
    end if;
  end process;

  -- Pick out clocks used from /2 chain
  prescaled(0)<=divided(0);
  prescaled(1)<=divided(1);
  prescaled(2)<=divided(3);
  prescaled(3)<=divided(4);

  -- Master clock is derived from clock selected by SPR1,SPR0 from the 4 
  -- prescaled clocks.
  spi_clk<=prescaled(to_natural(SPCR(SPR1 downto SPR0)));

  -- Generate clock for shift regsiter, only active during transmit or receive
  main_clock: process (state,spi_clk,sckin)
  begin
    if (state=TRAN_ST) then
      -- If master, then use internally generated clock
      clock<=spi_clk;
    elsif (state=RECV_ST) then
      -- If slave, then use sck input connected to masters clock
      clock<=sckin;
    else
      -- If idle, then clock is inactive (level determined by clock polarity)
      clock<=SPCR(CPOL);
    end if;
  end process;

  -- The edge which causes the shift depends on CPOL and CPHA
  shift_edge<=SPCR(CPOL) xor SPCR(CPHA);

  -- Count the number bits that have been shifted
  count: process(clock,state)
  begin
    if (state=WAIT_ST) then
      no_bits<=0;
    else
      if (clock=shift_edge and clock'event) then
              no_bits<=no_bits+1 after 1ns;
      end if;
    end if;
  end process;

  -- Output is just the 7th bit of the shift register or SPDR
  bit_out<=SPDR_shift(7);

  -- Detect edges for shifts.  This is complicated because when CPHA='1'
  -- the first edge must be ignored and there is no final edge
  process (clock,SPCR,SPSR)
  begin
    if (clock=shift_edge and clock'event) then
      if (SPCR(CPHA)='0' or no_bits/=0) then
        shift<=true;
      else
        shift<=false;
      end if;
    elsif (SPSR(SPIF)='1' and SPSR(SPIF)'event) then
      shift<=true;
    else
      shift<=false;
    end if;
  end process;

  -- Maintain the shift register part of SPDR
  do_shift: process (shift,E)
  begin
    if (state/=WAIT_ST) then
      SPDR_full<='0';
    end if;
    if (E='0' and E'event) then
      if (wr_enable='1' and address=16#2A#) then
        -- CPU write to the SPDR
        SPDR_shift<=bus_data;
        SPDR_full<='1';
      end if;
    end if;
    if (shift and shift'event) then
        -- Transfer next bit
        for i in byte'high downto byte'low+1 loop
          SPDR_shift(i)<=SPDR_shift(i-1);
        end loop;
        SPDR_shift(0)<=bit_in;
    end if;
  end process;

  -- Search for start of a receive for slave, this depends on the CPHA mode
  search_start: process(state,SPCR,ss,sckin)
  begin
    if (reset='0' or state/=WAIT_ST) then
      found_start<=false;
    else
      if (SPCR(CPHA)='0') then
        found_start<= ss='0' and ss'event;
      else
        found_start<= sckin/=SPCR(CPOL) and sckin'event;
      end if;
    end if;
  end process;

  -- A finite state machine to control the SPI
  fsm: process(reset,ph2)
    variable tmp : byte;
  begin
    set_SPIF<='0';
    if (reset='0') then
      state<=WAIT_ST;
    elsif (ph2='0' and ph2'event) then
      case state is
        when WAIT_ST => -- waiting
          if (SPCR(MSTR)='1') then
            -- Master starts transmiting when SPDR is written
            if (SPDR_full='1' and spi_clk=SPCR(CPOL)) then
              state<=TRAN_ST;
            end if;
          else
            -- Slave starts receiving after edge on ss or sck as detected by
            --   by the search_start process.
            if (found_start) then
              state<=RECV_ST;
            end if;
          end if;
        when TRAN_ST =>
          -- Transfer complete, if 8 bits have been sent
          if ((no_bits=8) and (clock=SPCR(CPOL))) then
            if (SPCR(CPHA)='0') then
              state<=WAIT_ST;
              SPDR_read<=SPDR_shift;
              set_SPIF<='1';
            else
              state<=DELAY1_ST;
            end if;
          end if;
        when RECV_ST =>
          -- Transfer complete, if 8 bits have been read
          if ((no_bits=8) and (clock=SPCR(CPOL))) then
            if (SPCR(CPHA)='0') then
              state<=DELAY3_ST;
            elsif (spi_clk=SPCR(CPOL)) then
              state<=DELAY2_ST;
            end if;
          end if;
        when DELAY1_ST =>
          if (spi_clk/=SPCR(CPOL)) then
            state<=WAIT_ST;
            SPDR_read<=SPDR_shift;
            set_SPIF<='1';
          end if;
        when DELAY2_ST =>
          if (spi_clk/=SPCR(CPOL)) then
            state<=DELAY3_ST;
          end if;
        when DELAY3_ST =>
          if (spi_clk=SPCR(CPOL)) then
            state<=WAIT_ST;
            SPDR_read<=SPDR_shift;
            set_SPIF<='1';
          end if;
      end case;
    end if;
  end process;

  -- Accept CPU writes to the SPCR control register
  SPCR_register: process (E,set_MODF)
  begin
    if (set_MODF='1' and set_MODF'event) then
      SPCR(MSTR)<='0';
    elsif (E='0' and E'event) then
      if (wr_enable='1' and address=16#28#) then
        SPCR<=bus_data;
      end if;
    end if;
  end process;

  -- Accept CPU writes to the SPSR register, and set flags in the SPSR
  --   based on the SPSR_set control signals.
  SPSR_register: process (reset,E,set_SPIF,set_MODF)
  begin
    if (E='0' and E'event) then
      if (wr_enable='1' and address=16#29#) then
        SPSR<=bus_data;
      elsif (wr_enable='1' and address=16#28# and SPSR_read(MODF)='1') then
        SPSR(MODF)<='0';        -- MODF clearing sequence
      end if;
    end if;
    if (set_SPIF='1' and set_SPIF'event) then
      SPSR(SPIF)<='1';
    end if;
    if (set_MODF='1' and set_MODF'event) then
      SPSR(MODF)<='1';
    end if;
    if (reset='0') then
      SPSR<=X"00";
    end if;
  end process;

  -- Detect mode faults
  process (SPCR,ss_dir,ss)
  begin 
    -- If configured as master, mode and ss are configured for input
    -- and if ss goes low, then there is a mode-fault error.
    if (SPCR(MSTR)='1' and ss_dir='0' and ss='0') then
      set_MODF<='1';
    else
      set_MODF<='0';
    end if;
  end process;

  -- Monitor system bus cycles for reads of SPI regsiters
  bus_cycle: process
  begin
    -- Wait for address strobe
    wait until as='0';

    -- Latch read/write enable and address signals
    rd_enable<=reg_en and bus_rw;
    wr_enable<=reg_en and not bus_rw;
    -- use natural for address to speed up simulation (faster comarisons)
    address<=to_natural(bus_addr(7 downto 0));  -- low byte of address

    -- Wait for start data valid
    wait until ph1='1';
    if (rd_enable='1' and reset='1') then
      -- If reading that drive bus with register data
      case address is
        when 16#28# =>
          bus_data<=SPCR;
        when 16#29# =>
          bus_data<=SPSR;
          SPSR_read<=SPSR;
        when 16#2A# =>
          bus_data<=SPDR_read;
        when others =>
          -- Ignore read of non-SPI register
          null;
      end case;
      wait until ph1='0';
      bus_data<=null;  -- Remove driver at end of data valid
    end if;
  end process;

end behavoir;
type b to return to text