neorv32/rtl/core/neorv32_gptmr.vhd

292 lines
13 KiB
VHDL

-- #################################################################################################
-- # << NEORV32 - General Purpose Timer (GPTMR) >> #
-- # ********************************************************************************************* #
-- # 32-bit timer with configurable clock prescaler, timer-threshold match and timer-capture #
-- # features. An interrupt can be triggered if the counter reaches a programmable threshold or if #
-- # a programmable edge is detects at the capture input. #
-- # ********************************************************************************************* #
-- # BSD 3-Clause License #
-- # #
-- # The NEORV32 RISC-V Processor, https://github.com/stnolting/neorv32 #
-- # Copyright (c) 2024, Stephan Nolting. All rights reserved. #
-- # #
-- # Redistribution and use in source and binary forms, with or without modification, are #
-- # permitted provided that the following conditions are met: #
-- # #
-- # 1. Redistributions of source code must retain the above copyright notice, this list of #
-- # conditions and the following disclaimer. #
-- # #
-- # 2. 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. #
-- # #
-- # 3. Neither the name of the copyright holder nor the names of its contributors may be used to #
-- # endorse or promote products derived from this software without specific prior written #
-- # permission. #
-- # #
-- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE #
-- # COPYRIGHT HOLDER OR CONTRIBUTORS 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. #
-- #################################################################################################
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library neorv32;
use neorv32.neorv32_package.all;
entity neorv32_gptmr is
port (
clk_i : in std_ulogic; -- global clock line
rstn_i : in std_ulogic; -- global reset line, low-active
bus_req_i : in bus_req_t; -- bus request
bus_rsp_o : out bus_rsp_t; -- bus response
clkgen_en_o : out std_ulogic; -- enable clock generator
clkgen_i : in std_ulogic_vector(7 downto 0);
irq_o : out std_ulogic; -- timer match interrupt
capture_i : in std_ulogic -- capture input
);
end neorv32_gptmr;
architecture neorv32_gptmr_rtl of neorv32_gptmr is
-- control register --
constant ctrl_en_c : natural := 0; -- r/w: GPTMR enable
constant ctrl_prsc0_c : natural := 1; -- r/w: clock prescaler select bit 0
constant ctrl_prsc1_c : natural := 2; -- r/w: clock prescaler select bit 1
constant ctrl_prsc2_c : natural := 3; -- r/w: clock prescaler select bit 2
constant ctrl_irqm_c : natural := 4; -- r/w: enable interrupt on timer match
constant ctrl_irqc_c : natural := 5; -- r/w: enable interrupt on capture trigger
constant ctrl_rise_c : natural := 6; -- r/w: capture on rising edge
constant ctrl_fall_c : natural := 7; -- r/w: capture on falling edge
constant ctrl_filter_c : natural := 8; -- r/w: filter capture input signal
--
constant ctrl_trigm_c : natural := 30; -- r/c: timer-match has fired, cleared by writing 0
constant ctrl_trigc_c : natural := 31; -- r/c: capture-trigger has fired, cleared by writing 0
--
signal ctrl : std_ulogic_vector(8 downto 0);
-- trigger flags --
signal trig_match, trig_capture : std_ulogic;
-- timer core --
type timer_t is record
count : std_ulogic_vector(31 downto 0); -- counter register
thres : std_ulogic_vector(31 downto 0); -- threshold register
tick : std_ulogic; -- clock generator tick
match : std_ulogic; -- count == thres
match_ff : std_ulogic;
trigger : std_ulogic; -- match trigger (single-shot)
cnt_we : std_ulogic; -- write access to count register
end record;
signal timer : timer_t;
-- sampling and capture logic --
type capture_t is record
sync : std_ulogic_vector(1 downto 0); -- synchronizer (prevent metastability)
sreg : std_ulogic_vector(3 downto 0); -- input sampling
state : std_ulogic_vector(1 downto 0); -- state buffer to detect edges
count : std_ulogic_vector(31 downto 0); -- capture register
trigger : std_ulogic; -- edge detector has been triggered
rising : std_ulogic; -- rising-edge detector
falling : std_ulogic; -- falling-edge detector
end record;
signal capture : capture_t;
begin
-- Bus Access -----------------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
bus_access: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
bus_rsp_o.ack <= '0';
bus_rsp_o.err <= '0';
bus_rsp_o.data <= (others => '0');
ctrl <= (others => '0');
trig_match <= '0';
trig_capture <= '0';
timer.cnt_we <= '0';
timer.thres <= (others => '0');
elsif rising_edge(clk_i) then
-- defaults --
bus_rsp_o.ack <= bus_req_i.stb;
bus_rsp_o.err <= '0'; -- no access error possible
bus_rsp_o.data <= (others => '0');
timer.cnt_we <= '0';
-- trigger flags --
trig_match <= ctrl(ctrl_en_c) and (trig_match or timer.trigger);
trig_capture <= ctrl(ctrl_en_c) and (trig_capture or capture.trigger);
-- actual bus access --
if (bus_req_i.stb = '1') then
-- write access --
if (bus_req_i.rw = '1') then
if (bus_req_i.addr(3 downto 2) = "00") then -- control register
ctrl(ctrl_en_c) <= bus_req_i.data(ctrl_en_c);
ctrl(ctrl_prsc0_c) <= bus_req_i.data(ctrl_prsc0_c);
ctrl(ctrl_prsc1_c) <= bus_req_i.data(ctrl_prsc1_c);
ctrl(ctrl_prsc2_c) <= bus_req_i.data(ctrl_prsc2_c);
ctrl(ctrl_irqm_c) <= bus_req_i.data(ctrl_irqm_c);
ctrl(ctrl_irqc_c) <= bus_req_i.data(ctrl_irqc_c);
ctrl(ctrl_rise_c) <= bus_req_i.data(ctrl_rise_c);
ctrl(ctrl_fall_c) <= bus_req_i.data(ctrl_fall_c);
ctrl(ctrl_filter_c) <= bus_req_i.data(ctrl_filter_c);
if (bus_req_i.data(ctrl_trigm_c) = '0') then -- clear by writing zero
trig_match <= '0';
end if;
if (bus_req_i.data(ctrl_trigc_c) = '0') then -- clear by writing zero
trig_capture <= '0';
end if;
end if;
if (bus_req_i.addr(3 downto 2) = "01") then -- threshold register
timer.thres <= bus_req_i.data;
end if;
if (bus_req_i.addr(3 downto 2) = "10") then -- counter register
timer.cnt_we <= '1';
end if;
-- read access --
else
case bus_req_i.addr(3 downto 2) is
when "00" => -- control register
bus_rsp_o.data(ctrl_en_c) <= ctrl(ctrl_en_c);
bus_rsp_o.data(ctrl_prsc0_c) <= ctrl(ctrl_prsc0_c);
bus_rsp_o.data(ctrl_prsc1_c) <= ctrl(ctrl_prsc1_c);
bus_rsp_o.data(ctrl_prsc2_c) <= ctrl(ctrl_prsc2_c);
bus_rsp_o.data(ctrl_irqm_c) <= ctrl(ctrl_irqm_c);
bus_rsp_o.data(ctrl_irqc_c) <= ctrl(ctrl_irqc_c);
bus_rsp_o.data(ctrl_rise_c) <= ctrl(ctrl_rise_c);
bus_rsp_o.data(ctrl_fall_c) <= ctrl(ctrl_fall_c);
bus_rsp_o.data(ctrl_filter_c) <= ctrl(ctrl_filter_c);
--
bus_rsp_o.data(ctrl_trigm_c) <= trig_match;
bus_rsp_o.data(ctrl_trigc_c) <= trig_capture;
when "01" => -- threshold register
bus_rsp_o.data <= timer.thres;
when "10" => -- counter register
bus_rsp_o.data <= timer.count;
when others => -- capture register
bus_rsp_o.data <= capture.count;
end case;
end if;
end if;
end if;
end process bus_access;
-- Timer Core -----------------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
counter_core: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
timer.count <= (others => '0');
timer.match_ff <= '0';
elsif rising_edge(clk_i) then
if (timer.cnt_we = '1') then -- write access
timer.count <= bus_req_i.data; -- data_i will remain stable for at least 1 cycle after WREN has returned to low
elsif (ctrl(ctrl_en_c) = '1') and (timer.tick = '1') then -- enabled and clock tick
if (timer.match = '1') then -- timer-match
timer.count <= (others => '0');
else
timer.count <= std_ulogic_vector(unsigned(timer.count) + 1);
end if;
end if;
timer.match_ff <= timer.match;
end if;
end process counter_core;
-- counter = threshold? --
timer.match <= '1' when (timer.count = timer.thres) else '0';
-- match edge detector --
timer.trigger <= '1' when (timer.match_ff = '0') and (timer.match = '1') else '0';
-- clock select --
clock_select: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
timer.tick <= '0';
elsif rising_edge(clk_i) then
timer.tick <= clkgen_i(to_integer(unsigned(ctrl(ctrl_prsc2_c downto ctrl_prsc0_c))));
end if;
end process clock_select;
-- clock generator enable --
clkgen_en_o <= ctrl(ctrl_en_c);
-- Capture Control ------------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
signal_capture: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
capture.sync <= (others => '0');
capture.sreg <= (others => '0');
capture.state <= (others => '0');
capture.count <= (others => '0');
elsif rising_edge(clk_i) then
-- synchronizer - prevent metastability --
capture.sync <= capture.sync(0) & capture_i;
-- sample shift register, running at reduced sample rate --
if (clkgen_i(clk_div4_c) = '1') then
capture.sreg <= capture.sreg(2 downto 0) & capture.sync(1);
end if;
-- sample state buffer --
capture.state(1) <= capture.state(0);
if (ctrl(ctrl_filter_c) = '0') then -- no filter, use synchronized input
capture.state(0) <= capture.sync(1);
else -- active filter: change state only if input is stable for 4 sample clocks
if (capture.sreg = "1111") then
capture.state(0) <= '1';
elsif (capture.sreg = "0000") then
capture.state(0) <= '0';
else
capture.state(0) <= capture.state(0);
end if;
end if;
-- timer capture --
if (ctrl(ctrl_en_c) = '1') and (capture.trigger = '1') then
capture.count <= timer.count;
end if;
end if;
end process signal_capture;
-- edge detectors --
capture.rising <= '1' when (capture.state = "01") else '0';
capture.falling <= '1' when (capture.state = "10") else '0';
-- capture trigger --
capture.trigger <= (ctrl(ctrl_rise_c) and capture.rising) or -- rising-edge trigger
(ctrl(ctrl_fall_c) and capture.falling); -- falling-edge trigger
-- Interrupt Generator --------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
irq_generator: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
irq_o <= '0';
elsif rising_edge(clk_i) then
irq_o <= ctrl(ctrl_en_c) and
((ctrl(ctrl_irqm_c) and timer.trigger) or -- timer-match interrupt
(ctrl(ctrl_irqc_c) and capture.trigger)); -- capture interrupt
end if;
end process irq_generator;
end neorv32_gptmr_rtl;