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

Subversion Repositories open8_urisc

[/] [open8_urisc/] [trunk/] [VHDL/] [o8_rtc.vhd] - Rev 223

Go to most recent revision | Compare with Previous | Blame | View Log

-- Copyright (c)2020 Jeremy Seth Henry
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--     * Redistributions of source code must retain the above copyright
--       notice, this list of conditions and the following disclaimer.
--     * Redistributions in binary form must reproduce the above copyright
--       notice, this list of conditions and the following disclaimer in the
--       documentation and/or other materials provided with the distribution,
--       where applicable (as part of a user interface, debugging port, etc.)
--
-- THIS SOFTWARE IS PROVIDED BY JEREMY SETH HENRY ``AS IS'' AND ANY
-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-- DISCLAIMED. IN NO EVENT SHALL JEREMY SETH HENRY BE LIABLE FOR ANY
-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-- THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--
-- VHDL Units :  o8_rtc
-- Description:  Provides automatically updated registers that maintain the
--            :   time of day. Keeps track of the day of week, hours, minutes
--            :   seconds, and tenths of a second in packed BCD format.
--            :  Module is doubled buffered to ensure time consistency during
--            :   accesses.
--            :  Also provides an 8-bit programmable periodic interrupt timer
--            :   with 1uS resolution, a 10uS fixed interrupt, as well as a
--            :   1 uSec tick (1 clock wide) for external use.
--
-- Register Map:
-- Offset  Bitfield Description                        Read/Write
--   0x0   AAAAAAAA Periodic Interval Timer in uS      (RW)
--   0x1   BBBBAAAA Tenths  (0x00 - 0x99)              (RW)
--   0x2   -BBBAAAA Seconds (0x00 - 0x59)              (RW)
--   0x3   -BBBAAAA Minutes (0x00 - 0x59)              (RW)
--   0x4   --BBAAAA Hours   (0x00 - 0x23)              (RW)
--   0x5   -----AAA Day of Week (0x00 - 0x06)          (RW)
--   0x6   -------- Update RTC regs from Shadow Regs   (WO)
--   0x7   A------- Update Shadow Regs from RTC regs   (RW)
--                  A = Update is Busy
 
library ieee;
use ieee.std_logic_1164.all;
  use ieee.std_logic_unsigned.all;
  use ieee.std_logic_arith.all;
  use ieee.std_logic_misc.all;
 
library work;
  use work.open8_pkg.all;
 
entity o8_rtc is
generic(
  Sys_Freq                   : real;
  Reset_Level                : std_logic;
  Address                    : ADDRESS_TYPE
);
port(
  Clock                      : in  std_logic;
  Reset                      : in  std_logic;
  uSec_Tick                  : out std_logic;
  --
  Open8_Bus                  : in  OPEN8_BUS_TYPE;
  Rd_Data                    : out DATA_TYPE;
  --
  Interrupt_PIT              : out std_logic;
  Interrupt_RTC              : out std_logic
);
end entity;
 
architecture behave of o8_rtc is
 
  constant User_Addr         : std_logic_vector(15 downto 3)
                               := Address(15 downto 3);
  alias  Comp_Addr           is Open8_Bus.Address(15 downto 3);
  signal Addr_Match          : std_logic;
 
  alias  Reg_Addr            is Open8_Bus.Address(2 downto 0);
  signal Reg_Addr_q          : std_logic_vector(2 downto 0);
 
  signal Wr_En               : std_logic;
  signal Wr_Data_q           : DATA_TYPE;
  signal Rd_En               : std_logic;
 
  constant DLY_1USEC_VAL     : integer := integer(Sys_Freq / 1000000.0);
  constant DLY_1USEC_WDT     : integer := ceil_log2(DLY_1USEC_VAL - 1);
  constant DLY_1USEC         : std_logic_vector :=
                     conv_std_logic_vector( DLY_1USEC_VAL - 1, DLY_1USEC_WDT);
 
  signal uSec_Cntr           : std_logic_vector( DLY_1USEC_WDT - 1 downto 0 )
                               := (others => '0');
  signal uSec_Tick_i         : std_logic;
 
  type PIT_TYPE is record
    timer_cnt                : DATA_TYPE;
    timer_ro                 : std_logic;
  end record;
 
  signal pit                 : PIT_TYPE;
 
  type RTC_TYPE is record
    frac                     : std_logic_vector(15 downto 0);
    frac_ro                  : std_logic;
 
    tens_l                   : std_logic_vector(3 downto 0);
    tens_l_ro                : std_logic;
 
    tens_u                   : std_logic_vector(3 downto 0);
    tens_u_ro                : std_logic;
 
    secs_l                   : std_logic_vector(3 downto 0);
    secs_l_ro                : std_logic;
 
    secs_u                   : std_logic_vector(3 downto 0);
    secs_u_ro                : std_logic;
 
    mins_l                   : std_logic_vector(3 downto 0);
    mins_l_ro                : std_logic;
 
    mins_u                   : std_logic_vector(3 downto 0);
    mins_u_ro                : std_logic;
 
    hours_l                  : std_logic_vector(3 downto 0);
    hours_l_ro               : std_logic;
 
    hours_u                  : std_logic_vector(3 downto 0);
    hours_u_ro               : std_logic;
 
    dow                      : std_logic_vector(2 downto 0);
  end record;
 
  constant DECISEC           : std_logic_vector(15 downto 0) :=
                                conv_std_logic_vector(10000,16);
 
  signal rtc                 : RTC_TYPE;
 
  signal interval            : DATA_TYPE;
  signal update_interval     : std_logic;
 
  signal shd_tens            : DATA_TYPE;
  signal shd_secs            : DATA_TYPE;
  signal shd_mins            : DATA_TYPE;
  signal shd_hours           : DATA_TYPE;
  signal shd_dow             : DATA_TYPE;
 
  signal update_rtc          : std_logic;
  signal update_shd          : std_logic;
  signal update_ctmr         : std_logic_vector(3 downto 0);
 
begin
 
  uSec_Tick                  <= uSec_Tick_i;
  Addr_Match                 <= '1' when Comp_Addr = User_Addr else '0';
 
  Interrupt_PIT              <= pit.timer_ro;
  Interrupt_RTC              <= rtc.frac_ro;
 
  io_reg: process( Clock, Reset )
  begin
    if( Reset = Reset_Level )then
      uSec_Cntr              <= (others => '0');
      uSec_Tick_i            <= '0';
 
      pit.timer_cnt          <= x"00";
      pit.timer_ro           <= '0';
 
      rtc.frac               <= DECISEC;
      rtc.frac_ro            <= '0';
 
      rtc.tens_l             <= (others => '0');
      rtc.tens_l_ro          <= '0';
 
      rtc.tens_u             <= (others => '0');
      rtc.tens_u_ro          <= '0';
 
      rtc.secs_l             <= (others => '0');
      rtc.secs_l_ro          <= '0';
 
      rtc.secs_u             <= (others => '0');
      rtc.secs_u_ro          <= '0';
 
      rtc.mins_l             <= (others => '0');
      rtc.mins_l_ro          <= '0';
 
      rtc.mins_u             <= (others => '0');
      rtc.mins_u_ro          <= '0';
 
      rtc.hours_l            <= (others => '0');
      rtc.hours_l_ro         <= '0';
 
      rtc.hours_u            <= (others => '0');
      rtc.hours_u_ro         <= '0';
 
      rtc.dow                <= (others => '0');
 
      shd_tens               <= (others => '0');
      shd_secs               <= (others => '0');
      shd_mins               <= (others => '0');
      shd_hours              <= (others => '0');
      shd_dow                <= (others => '0');
 
      update_rtc             <= '0';
      update_shd             <= '0';
      update_ctmr            <= (others => '0');
 
      interval               <= x"00";
      update_interval        <= '0';
 
      Wr_Data_q              <= (others => '0');
      Reg_Addr_q             <= (others => '0');
      Wr_En                  <= '0';
      Rd_En                  <= '0';
      Rd_Data                <= OPEN8_NULLBUS;
 
    elsif( rising_edge( Clock ) )then
 
      uSec_Cntr              <= uSec_Cntr - 1;
      uSec_Tick_i            <= '0';
      if( uSec_Cntr = 0 )then
        uSec_Cntr            <= DLY_1USEC;
        uSec_Tick_i          <= '1';
      end if;
 
      -- Periodic Interval Timer
      pit.timer_cnt          <= pit.timer_cnt - uSec_Tick_i;
      pit.timer_ro           <= '0';
      if( update_interval = '1' )then
        pit.timer_cnt        <= interval;
      elsif( or_reduce(pit.timer_cnt) = '0' )then
        pit.timer_cnt        <= interval;
        pit.timer_ro         <= or_reduce(interval);
      end if;
 
      -- Fractional decisecond counter - cycles every 10k microseconds
      rtc.frac               <= rtc.frac - uSec_Tick_i;
      rtc.frac_ro            <= '0';
      if( or_reduce(rtc.frac) = '0' or update_rtc = '1' )then
        rtc.frac             <= DECISEC;
        rtc.frac_ro          <= not update_rtc;
      end if;
 
      -- Decisecond counter (lower)
      rtc.tens_l             <= rtc.tens_l + rtc.frac_ro;
      rtc.tens_l_ro          <= '0';
      if( update_rtc = '1' )then
        rtc.tens_l           <= shd_tens(3 downto 0);
      elsif( rtc.tens_l > x"9")then
        rtc.tens_l           <= (others => '0');
        rtc.tens_l_ro        <= '1';
      end if;
 
      -- Decisecond counter (upper)
      rtc.tens_u             <= rtc.tens_u + rtc.tens_l_ro;
      rtc.tens_u_ro          <= '0';
      if( update_rtc = '1' )then
        rtc.tens_u           <= shd_tens(7 downto 4);
      elsif( rtc.tens_u > x"9")then
        rtc.tens_u           <= (others => '0');
        rtc.tens_u_ro        <= '1';
      end if;
 
      -- Second counter (lower)
      rtc.secs_l             <= rtc.secs_l + rtc.tens_u_ro;
      rtc.secs_l_ro          <= '0';
      if( update_rtc = '1' )then
        rtc.secs_l           <= shd_secs(3 downto 0);
      elsif( rtc.secs_l > x"9")then
        rtc.secs_l           <= (others => '0');
        rtc.secs_l_ro        <= '1';
      end if;
 
      -- Second counter (upper)
      rtc.secs_u             <= rtc.secs_u + rtc.secs_l_ro;
      rtc.secs_u_ro          <= '0';
      if( update_rtc = '1' )then
        rtc.secs_u           <= shd_secs(7 downto 4);
      elsif( rtc.secs_u > x"5")then
        rtc.secs_u           <= (others => '0');
        rtc.secs_u_ro        <= '1';
      end if;
 
      -- Minutes counter (lower)
      rtc.mins_l             <= rtc.mins_l + rtc.secs_u_ro;
      rtc.mins_l_ro          <= '0';
      if( update_rtc = '1' )then
        rtc.mins_l           <= shd_mins(3 downto 0);
      elsif( rtc.mins_l > x"9")then
        rtc.mins_l           <= (others => '0');
        rtc.mins_l_ro        <= '1';
      end if;
 
      -- Minutes counter (upper)
      rtc.mins_u             <= rtc.mins_u + rtc.mins_l_ro;
      rtc.mins_u_ro          <= '0';
      if( update_rtc = '1' )then
        rtc.mins_u           <= shd_mins(7 downto 4);
      elsif( rtc.mins_u > x"5")then
        rtc.mins_u           <= (others => '0');
        rtc.mins_u_ro        <= '1';
      end if;
 
      -- Hour counter (lower)
      rtc.hours_l            <= rtc.hours_l + rtc.mins_u_ro;
      rtc.hours_l_ro         <= '0';
      if( update_rtc = '1' )then
        rtc.hours_l          <= shd_hours(3 downto 0);
      elsif( rtc.hours_l > x"9")then
        rtc.hours_l          <= (others => '0');
        rtc.hours_l_ro       <= '1';
      end if;
 
      -- Hour counter (upper)
      rtc.hours_u            <= rtc.hours_u + rtc.hours_l_ro;
      if( update_rtc = '1' )then
        rtc.hours_u          <= shd_hours(7 downto 4);
      end if;
 
      rtc.hours_u_ro         <= '0';
      if( rtc.hours_u >= x"2" and rtc.hours_l > x"3" )then
        rtc.hours_l          <= (others => '0');
        rtc.hours_u          <= (others => '0');
        rtc.hours_u_ro       <= '1';
      end if;
 
      -- Day of Week counter
      rtc.dow                <= rtc.dow + rtc.hours_u_ro;
      if( update_rtc = '1' )then
        rtc.dow              <= shd_dow(2 downto 0);
      elsif( rtc.dow = x"07")then
        rtc.dow              <= (others => '0');
      end if;
 
      -- Copy the RTC registers to the shadow registers when the coherency
      --  timer is zero (RTC registers are static)
      if( update_shd = '1' and or_reduce(update_ctmr) = '0' )then
        shd_tens             <= rtc.tens_u & rtc.tens_l;
        shd_secs             <= rtc.secs_u & rtc.secs_l;
        shd_mins             <= rtc.mins_u & rtc.mins_l;
        shd_hours            <= rtc.hours_u & rtc.hours_l;
        shd_dow              <= "00000" & rtc.dow;
        update_shd           <= '0';
      end if;
 
      update_interval        <= '0';
 
      Reg_Addr_q             <= Reg_Addr;
      Wr_Data_q              <= Open8_Bus.Wr_Data;
 
      Wr_En                  <= Addr_Match and Open8_Bus.Wr_En;
      update_rtc             <= '0';
      if( Wr_En = '1' )then
        case( Reg_Addr_q )is
          when "000" =>
            interval         <= Wr_Data_q;
            update_interval  <= '1';
 
          when "001" =>
            shd_tens         <= Wr_Data_q;
 
          when "010" =>
            shd_secs         <= Wr_Data_q;
 
          when "011" =>
            shd_mins         <= Wr_Data_q;
 
          when "100" =>
            shd_hours        <= Wr_Data_q;
 
          when "101" =>
            shd_dow          <= Wr_Data_q;
 
          when "110" =>
            update_rtc       <= '1';
 
          when "111" =>
            update_shd  <= '1';
 
          when others => null;
        end case;
      end if;
 
      -- Coherency timer - ensures that the shadow registers are updated with
      --  valid time data by delaying updates until the rtc registers have
      --  finished cascading.
      update_ctmr            <= update_ctmr - or_reduce(update_ctmr);
      if( rtc.frac_ro = '1' )then
        update_ctmr          <= (others => '1');
      end if;
 
      Rd_Data                <= OPEN8_NULLBUS;
      Rd_En                  <= Addr_Match and Open8_Bus.Rd_En;
      if( Rd_En = '1' )then
        case( Reg_Addr_q )is
          when "000" =>
            Rd_Data          <= interval;
          when "001" =>
            Rd_Data          <= shd_tens;
          when "010" =>
            Rd_Data          <= shd_secs;
          when "011" =>
            Rd_Data          <= shd_mins;
          when "100" =>
            Rd_Data          <= shd_hours;
          when "101" =>
            Rd_Data          <= shd_dow;
          when "110" =>
            null;
          when "111" =>
            Rd_Data          <= update_shd & "0000000";
          when others => null;
        end case;
      end if;
 
    end if;
  end process;
 
end architecture;

Go to most recent revision | 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.