/*
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                                                                                                 ┃
┃          Program Name: IntervalTimer.v                                                          ┃
┃                Author: Michael Krämer                                                           ┃
┃                 Email: Michael@dk8pp.de                                                         ┃
┃                  Date: 22.11.2025                                                               ┃
┃                  Tool: Gowin FPGA Designer V1.9.11.01 Education                                 ┃
┃                Device: GW5A-LV25MG121 Typ A                                                     ┃
┃               Version: 0.1                                                                      ┃
┃        Program status: still testing                                                            ┃
┃                                                                                                 ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃                                                                                                 ┃
┃  Purpose: Interval timer                                                                        ┃
┃                                                                                                 ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃                                                                                                 ┃
┃  © Copyright 2025-2026 Michael Krämer, DK8PP                                                    ┃
┃                                                                                                 ┃
┃  This program is free software: you can redistribute it and/or modify it under the terms of     ┃
┃  the GNU Lesser General Public License as published by the Free Software Foundation, either     ┃
┃  version 3 of the License, or (at your option) any later version.                               ┃
┃                                                                                                 ┃
┃  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;      ┃
┃  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.      ┃
┃  See the GNU Lesser General Public License for more details.                                    ┃
┃                                                                                                 ┃
┃  You should have received a copy of the GNU Lesser General Public License along with this       ┃
┃  program.   If not, see <http://www.gnu.org/licenses/>.                                         ┃
┃                                                                                                 ┃
┃  ============================================================================================   ┃
┃                                                                                                 ┃
┃  Dieses Programm ist Freie Software: Sie können es unter den Bedingungen der GNU Lesser         ┃
┃  General Public License, wie von der Free Software Foundation, Version 3 der Lizenz oder        ┃
┃  (nach Ihrer Wahl) jeder neueren veröffentlichten Version, weiter verteilen und/oder            ┃
┃  modifizieren.                                                                                  ┃
┃                                                                                                 ┃
┃  Dieses Programm wird in der Hoffnung bereitgestellt, dass es nützlich sein wird, jedoch OHNE   ┃
┃  JEDE GEWÄHR,; sogar ohne die implizite Gewähr der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN        ┃
┃  BESTIMMTEN ZWECK. Siehe die GNU Lesser General Public License für weitere Einzelheiten.        ┃
┃                                                                                                 ┃
┃  Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit diesem Programm      ┃
┃  erhalten haben. Wenn nicht, siehe <https://www.gnu.org/licenses/>.                             ┃
┃                                                                                                 ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

When enabled, this interval timer generates interrupts in programmable intervals. It is
 initialized with the programmable reload value in the cnt_reload register and is decremented with
 the CPU clock. When it reaches zero, it generates an interrupt and it is reloaded again with the
 reload value. Unlike the CPU timer, it does not have to be reloaded by the CPU after expiration.

*/

module IntervalTimer
    #(parameter REG_BITSIZE = 32,       /* bitsize of registers */
      RELOAD = 32'd199_999,             /* default reload value, 10 ms interval at 20 MHz CPU clock */
      ADSIZE = 4                        /* default size of address decoder */
    )
    (input  wire cpuclk,                /* cpu clock */
     input  wire reset,                 /* reset input, active high */
     input  wire [ADSIZE-1:0] addr_in,  /* address input */
     input  wire [31:0] data_in,        /* data input */
     output wire [31:0] data_out,       /* data output */
     input  wire mod_sel,               /* module select */
     input  wire cpu_write,             /* CPU write cycle */
     output wire intrq                  /* interrupt request */
    ) ;

    reg [REG_BITSIZE-1:0] cnt_reload ;  /* counter reload value */
    reg [REG_BITSIZE-1:0] cnt ;         /* counter */
    reg intrq_reg ;                     /* interrupt request flag */
    reg enable ;                        /* enable interval timer if "1" */

assign intrq = intrq_reg ;

/*
╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
│       Address mapping                                                                           │
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯

IntervalTimer register address offsets:

IT_RLD_OFFSET:  Timer reload value (R/W)
    This is a 32-bit value and it holds the reload value for the timer

IT_ENABLE_OFFSET:  Enable timer (R/W)
    Bit 0 enables (1) or disables (0) the timer. The timer is disabled after reset.

IT_ACK_OFFSET: Interrupt acknowledge (W)
    Write bit 0 = 1 to acknowledge a pending interval timer interrupt

*/

localparam IT_RLD_OFFSET =      32'h00 ;    /* Offset to timer reload value register */
localparam IT_CONFIG_OFFSET =   32'h04 ;    /* Offset to enable register */
localparam IT_ACK_OFFSET =      32'h08 ;    /* Offset to interrupt acknowledge register */

wire [ADSIZE-1:0] addr_rld      = IT_RLD_OFFSET ;
wire [ADSIZE-1:0] addr_config   = IT_CONFIG_OFFSET ;
wire [ADSIZE-1:0] addr_ack      = IT_ACK_OFFSET ;

wire wr_rld =    cpu_write & mod_sel & (addr_in[ADSIZE-1:0] == addr_rld) ;    /* write reload register */
wire wr_cfg =    cpu_write & mod_sel & (addr_in[ADSIZE-1:0] == addr_config) ; /* write config register */
wire wr_intack = cpu_write & mod_sel & data_in[0] & (addr_in[ADSIZE-1:0] == addr_ack) ;    /* acknowledge interrupt */

wire rd_rld = ~cpu_write & mod_sel & (addr_in[ADSIZE-1:0] == addr_rld) ;    /* read reload register */
wire rd_cfg = ~cpu_write & mod_sel & (addr_in[ADSIZE-1:0] == addr_config) ; /* read config register */

/* read data */
assign data_out = rd_rld ? cnt_reload :
                  rd_cfg ? {31'b0, enable} :
                  32'h00 ;

/*
╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
│       cpuclk clock domain                                                                       │
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
*/
always @(posedge cpuclk)
begin
   if (reset)
   begin
      intrq_reg <= 0 ;          /* interrupt request from interval timer */
      enable <= 0 ;             /* disable interval timer */
      cnt <= RELOAD ;           /* counter for interval timer */
      cnt_reload <= RELOAD ;    /* counter reload value */
   end
   else
   begin
      if (wr_intack) intrq_reg <= 0 ; /* remove interrupt request when acknowledged by interrupt handler */
      if (wr_cfg) enable <= data_in[0] ; /* enable/disable */
      if (wr_rld) cnt_reload <= data_in ; /* write reload register */
      if (enable)
      begin /* execute only if enabled */
         if (cnt == 0)
         begin
            cnt <= cnt_reload ;
            intrq_reg <= 1 ;
         end
         else
         begin
            cnt <= cnt - 1 ;
         end
      end
   end
end

endmodule

