292 lines
13 KiB
VHDL
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;
|