Timer module rewrite, APU clocked from timer (#280)

* Rework timer modules to resolve #101

Sound clock is passed to the APU but is not currently being used. Work needed to match this up.

* Rename APU frame clock.

* VHDL-2008 fix.

* Fix APU clock to 1x CPU clock.

* Clarify machine cycles in timer.

* Whitespace

* Add APU channel enables for debugging.

* Fix timer generated sound clk

R14 - Audio issues remain for 2X CPU games

* Fix issue with 2x gameboy APU sound clock

R22

* Re-implement sound clocking in timer module.

R25, Remove old code N.B. Likely a bug still here.

* Fix timer generated APU clock to 4 MiHz cycle.

R27

* Tidy up.

Remove attribution in timer module since doing a full rewrite.

* Disabling TAC can generate a clock-edge

Thanks to @paulb-nl for pointing out

* Prevent name clash

* Rework timer modules to resolve #101

Sound clock is passed to the APU but is not currently being used. Work needed to match this up.

* Rename APU frame clock.

* VHDL-2008 fix.

* Fix APU clock to 1x CPU clock.

* Clarify machine cycles in timer.

* Whitespace

* Add APU channel enables for debugging.

* Fix timer generated sound clk

R14 - Audio issues remain for 2X CPU games

* Fix issue with 2x gameboy APU sound clock

R22

* Re-implement sound clocking in timer module.

R25, Remove old code N.B. Likely a bug still here.

* Fix timer generated APU clock to 4 MiHz cycle.

R27

* Tidy up.

Remove attribution in timer module since doing a full rewrite.

* Disabling TAC can generate a clock-edge

Thanks to @paulb-nl for pointing out

* Prevent name clash

* Remove unused variable

* Set en_len_r on same cycle as en_len

* Buffer en_len to en_len_r properly

* Remove debug tools
This commit is contained in:
Mark Johnson
2026-03-23 00:32:35 +11:00
committed by GitHub
parent f36baf98ad
commit 1b131a0de1
5 changed files with 195 additions and 253 deletions

View File

@@ -269,7 +269,6 @@ localparam CONF_STR = {
"P3OR,Rewind Capture,Off,On;",
"P3-;",
"P3o3,Super Game Boy + GBC,Off,On;",
"-;",
"R0,Reset;",

View File

@@ -16,7 +16,7 @@ package pBus_savestates is
upper : integer range 0 to BUS_buswidth-1;
lower : integer range 0 to BUS_buswidth-1;
size : integer range 0 to (2**BUS_busadr)-1;
default : std_logic_vector(BUS_buswidth-1 downto 0);
def : std_logic_vector(BUS_buswidth-1 downto 0);
end record;
end package;
@@ -146,7 +146,7 @@ begin
Adr => Reg.Adr,
upper => Reg.upper,
lower => Reg.lower,
def => Reg.default
def => Reg.def
)
port map
(

View File

@@ -151,7 +151,8 @@ reg [1:0] ff4c_key0; // GBC DMG mode register
wire isGBC_mode = !ff4c_key0 | boot_rom_enabled;
wire [15:0] cpu_addr;
wire [7:0] cpu_do;
wire [7:0] cpu_do;
wire apu_framecount_en;
wire sel_timer = (cpu_addr[15:4] == 12'hff0) && (cpu_addr[3:2] == 2'b01);
wire sel_video_reg = (cpu_addr[15:4] == 12'hff4) || (isGBC && (cpu_addr[15:4] == 12'hff6) && (cpu_addr[3:0] >= 4'h8 && cpu_addr[3:0] <= 4'hC)); //video and oam dma (+ ff68-ff6C when gbc)
@@ -438,15 +439,15 @@ end
// --------------------------------------------------------------------
// ------------------------------ audio -------------------------------
// --------------------------------------------------------------------
wire audio_rd = !cpu_rd_n && sel_audio;
wire audio_wr = !cpu_wr_n_edge && sel_audio;
wire audio_wr = !cpu_wr_n && sel_audio;
gbc_snd audio (
.clk ( clk_sys ),
.ce ( ce_2x ),
.ce ( ce ),
.reset ( reset_ss ),
.apu_framecount_en ( apu_framecount_en ),
.is_gbc ( isGBC ),
.remove_pops ( audio_no_pops ),
@@ -650,9 +651,11 @@ end
timer timer (
.reset ( reset_ss ),
.clk_sys ( clk_sys ),
.ce ( ce_cpu ), //2x in fast mode
.clk_sys ( clk_sys ),
.ce ( ce_cpu ), // 2x in fast mode
.ce_4MHz (ce), // Always 4 MiHz
.cpu_speed ( cpu_speed ),
.irq ( timer_irq ),
.cpu_sel ( sel_timer ),
@@ -660,6 +663,7 @@ timer timer (
.cpu_wr ( !cpu_wr_n_edge ),
.cpu_di ( cpu_do ),
.cpu_do ( timer_do ),
.apu_framecount_en ( apu_framecount_en),
.SaveStateBus_Din (SaveStateBus_Din ),
.SaveStateBus_Adr (SaveStateBus_Adr ),

View File

@@ -1,8 +1,3 @@
-- Notes:
-- When the output DAC of each channel is disabled, the real voltage will drift
-- to 0 V at some unknown rate. Here, it is implemented as an immediate return to "0 V".
-- If this is found to not be accurate enough, an additional timer may be added.
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
@@ -14,9 +9,10 @@ use work.pReg_savestates.all;
library work;
entity gbc_snd is
port (
clk : in std_logic;
ce : in std_logic;
reset : in std_logic;
clk : in std_logic;
ce : in std_logic;
apu_framecount_en : in std_logic;
reset : in std_logic;
is_gbc : in std_logic;
remove_pops : in std_logic; -- 0: Accurate output, 1: Toggling DACs will not cause pops but some audio can be inaccurate.
@@ -56,10 +52,8 @@ architecture SYN of gbc_snd is
subtype wav_t is std_logic_vector(3 downto 0);
type wav_arr_t is array(0 to 31) of wav_t;
signal en_snd : std_logic; -- Enable at base sound frequency (4.19MHz)
signal en_snd2 : std_logic; -- Enable at clk/2
signal en_snd4 : std_logic; -- Enable at clk/4
signal en_512 : std_logic; -- 512Hz enable
signal en_snden2 : std_logic; -- Enable at clk/2
signal en_snden4 : std_logic; -- Enable at clk/4
@@ -219,23 +213,10 @@ begin
SaveStateBus_Dout <= wired_or;
end process;
en_snd2 <= en_snd and en_snden2;
en_snd4 <= en_snd and en_snden4;
en_snd2 <= ce and en_snden2;
en_snd4 <= ce and en_snden4;
process (clk)
begin
if rising_edge(clk) then
if reset = '1' then
en_snd <= SS_Sound1(0); --'0';
else
if ce = '1' then
en_snd <= not en_snd;
end if;
end if;
end if;
end process;
SS_Sound1_BACK( 0) <= en_snd;
SS_Sound1_BACK( 0) <= '0';
SS_Sound1_BACK(3 downto 1) <= std_logic_vector(to_unsigned(framecnt, 3));
SS_Sound1_BACK( 4) <= en_len_r ;
SS_Sound1_BACK( 5) <= en_snden2;
@@ -243,19 +224,16 @@ begin
SS_Sound1_BACK( 7) <= en_len ;
SS_Sound1_BACK( 8) <= en_env ;
SS_Sound1_BACK( 9) <= en_sweep ;
SS_Sound1_BACK( 10) <= en_512 ;
SS_Sound1_BACK( 10) <= '0' ;
-- Calculate divided and frame sequencer clock enables
process (clk)
frame_sequencer : process (clk)
variable clkcnt : unsigned(1 downto 0);
variable cnt_512 : unsigned(12 downto 0);
variable temp_512 : unsigned(13 downto 0);
begin
if rising_edge(clk) then
if reset = '1' then
clkcnt := "00";
cnt_512 := (others => '0');
framecnt <= to_integer(unsigned(SS_Sound1(3 downto 1))); -- 0;
en_len_r <= SS_Sound1( 4); -- '0';
en_snden2 <= SS_Sound1( 5); -- '0';
@@ -263,10 +241,8 @@ begin
en_len <= SS_Sound1( 7); -- '0';
en_env <= SS_Sound1( 8); -- '0';
en_sweep <= SS_Sound1( 9); -- '0';
en_512 <= SS_Sound1(10); -- '0';
elsif snd_enable = '0' then --only clock frame sequencer if sound is enabled, restart at 0
clkcnt := "00";
cnt_512 := (others => '0');
framecnt <= 0;
en_len_r <= '0';
en_snden2 <= '0';
@@ -274,57 +250,51 @@ begin
en_len <= '0';
en_env <= '0';
en_sweep <= '0';
en_512 <= '0';
elsif ce = '1' then
-- Base clock divider
if en_snd = '1' then
else
-- Frame sequencer (length, envelope, sweep) clock enables
if ce = '1' then
en_len <= '0';
en_env <= '0';
en_sweep <= '0';
en_len_r <= en_len; -- For reg write quirks
if apu_framecount_en = '1' then
if framecnt = 0 or framecnt = 2 or framecnt = 4 or framecnt = 6 then
en_len <= '1';
end if;
if framecnt = 2 or framecnt = 6 then
en_sweep <= '1';
end if;
if framecnt = 7 then
en_env <= '1';
end if;
if framecnt < 7 then
framecnt <= framecnt + 1;
else
framecnt <= 0;
end if;
end if;
end if;
if ce = '1' then
-- Base clock divider
clkcnt := clkcnt + 1;
if clkcnt(0) = '1' then
en_snden2 <= '1';
else
en_snden2 <= '0';
end if;
if clkcnt = "11" then
en_snden4 <= '1';
else
en_snden4 <= '0';
end if;
end if;
-- Frame sequencer (length, envelope, sweep) clock enables
en_len <= '0';
en_env <= '0';
en_sweep <= '0';
if en_512 = '1' then
en_len_r <= not en_len_r;
if framecnt = 0 or framecnt = 2 or framecnt = 4 or framecnt = 6 then
en_len <= '1';
en_len_r <= not en_len_r;
end if;
if framecnt = 2 or framecnt = 6 then
en_sweep <= '1';
end if;
if framecnt = 7 then
en_env <= '1';
end if;
if framecnt < 7 then
framecnt <= framecnt + 1;
else
framecnt <= 0;
end if;
end if;
--
en_512 <= '0';
if en_snd = '1' then
temp_512 := ('0' & cnt_512) + to_unsigned(1, temp_512'length);
cnt_512 := temp_512(temp_512'high - 1 downto temp_512'low);
en_512 <= temp_512(13);
end if;
end if;
end if;
end process;
end process;
SS_Sound1_BACK(13 downto 11) <= sq1_swper ;
SS_Sound1_BACK( 14) <= sq1_swdir ;
@@ -369,6 +339,19 @@ begin
SS_Sound3_BACK( 7 downto 0) <= ch_map;
SS_Sound3_BACK(15 downto 8) <= ch_vol;
SS_Sound3_BACK(22 downto 16) <= sq1_slen;
SS_Sound3_BACK( 23) <= sq1_playing;
SS_Sound3_BACK(34 downto 24) <= sq1_fr2;
SS_Sound3_BACK(38 downto 35) <= sq1_vol;
SS_Sound3_BACK( 39) <= sq2_playing;
SS_Sound3_BACK(43 downto 40) <= sq2_vol;
SS_Sound3_BACK(45 downto 44) <= "00"; -- unused
SS_Sound3_BACK(49 downto 46) <= wav_wav_r;
SS_Sound3_BACK( 50) <= wav_playing;
SS_Sound3_BACK(55 downto 51) <= std_logic_vector(wav_index);
SS_Sound3_BACK(57 downto 56) <= std_logic_vector(wav_access);
SS_Sound3_BACK( 58) <= noi_playing;
SS_Sound3_BACK(62 downto 59) <= noi_vol;
wav_ram_savestate : for k in 0 to 15 generate
SS_Wave1_BACK(4*(k+1) - 1 downto 4*k) <= wav_ram(k);
@@ -819,19 +802,6 @@ begin
end process read_registers;
SS_Sound3_BACK( 23) <= sq1_playing;
SS_Sound3_BACK(34 downto 24) <= sq1_fr2;
SS_Sound3_BACK(38 downto 35) <= sq1_vol;
SS_Sound3_BACK( 39) <= sq2_playing;
SS_Sound3_BACK(43 downto 40) <= sq2_vol;
SS_Sound3_BACK(45 downto 44) <= "00"; -- unused
SS_Sound3_BACK(49 downto 46) <= wav_wav_r;
SS_Sound3_BACK( 50) <= wav_playing;
SS_Sound3_BACK(55 downto 51) <= std_logic_vector(wav_index);
SS_Sound3_BACK(57 downto 56) <= std_logic_vector(wav_access);
SS_Sound3_BACK( 58) <= noi_playing;
SS_Sound3_BACK(62 downto 59) <= noi_vol;
square1 : process(clk, sq1_vol, sq1_svol, sq1_envsgn, sq1_out, sq1_suppressed)
constant duty_0 : std_logic_vector(0 to 7) := "00000001";
constant duty_1 : std_logic_vector(0 to 7) := "10000001";

View File

@@ -1,183 +1,152 @@
//
// timer.v
//
// Gameboy for the MIST board https://github.com/mist-devel
//
// Copyright (c) 2015 Till Harbaum <till@harbaum.org>
//
// This source file is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Implementation follows the gbdev pandocs
// https://gbdev.io/pandocs/Timer_Obscure_Behaviour.html
module timer (
input reset,
input clk_sys,
input ce, // 4 Mhz cpu clock
output reg irq,
input reset,
input clk_sys,
input ce, // 4 MiHz / 8 MiHz cpu clock
input ce_4MHz,
input cpu_speed,
output irq,
// cpu register interface
input cpu_sel,
input [1:0] cpu_addr,
input cpu_wr,
input [7:0] cpu_di,
// CPU register interface
input cpu_sel,
input [1:0] cpu_addr,
input cpu_wr,
input [7:0] cpu_di,
output [7:0] cpu_do,
output apu_framecount_en,
// savestates
// Save states
input [63:0] SaveStateBus_Din,
input [9:0] SaveStateBus_Adr,
input SaveStateBus_wren,
input SaveStateBus_rst,
output [63:0] SaveStateBus_Dout
);
assign cpu_do =
(cpu_addr == 2'b00) ? div :
(cpu_addr == 2'b01) ? tima :
(cpu_addr == 2'b10) ? tma :
{5'b11111, tac};
// savestates
wire [46:0] SS_Timer;
wire [46:0] SS_Timer_BACK;
// https://gbdev.io/pandocs/Timer_Obscure_Behaviour.html#timer-global-circuit
// Falling-edge of selected counter
assign apu_framecount_en = clk_sound_r && !clk_sound;
eReg_SavestateV #(0, 6, 46, 0, 64'h0000000000000008) iREG_SAVESTATE_Timer (clk_sys, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_Dout, SS_Timer_BACK, SS_Timer);
reg clk_sound_r;
wire clk_sound = cpu_speed ? div[5] : div[4];
// input: 4Mhz
// clk_div[0] = 2Mhz
// clk_div[1] = 1Mhz
// clk_div[2] = 524khz
// clk_div[3] = 262khz
// clk_div[4] = 131khz
// clk_div[5] = 65khz
// clk_div[6] = 32khz
// clk_div[7] = 16khz
// clk_div[8] = 8khz
// clk_div[9] = 4khz
// Use 4 MiHz clock to generate APU trigger to enforce alignment.
always @(posedge clk_sys) begin : CLK_SOUND_BLK
if (reset)
clk_sound_r <= 1'b0;
else if (ce_4MHz)
clk_sound_r <= clk_sound;
end
wire resetdiv = cpu_sel && cpu_wr && (cpu_addr == 2'b00); //resetdiv also resets internal counter
// Save states
wire [46:0] SS_Timer;
wire [46:0] SS_Timer_BACK;
reg [9:0] clk_div;
reg clk_div_1_9;
reg clk_div_1_3;
reg clk_div_1_5;
reg clk_div_1_7;
always @(posedge clk_sys)
if (reset)
clk_div <= SS_Timer[9:0]; // 10'd8;
else if(resetdiv)
clk_div <= 10'd2;
else if (ce)
clk_div <= clk_div + 10'd1;
eReg_SavestateV #(0, 6, 46, 0, 64'h0000000000000008) iREG_SAVESTATE_Timer (clk_sys, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_Dout, SS_Timer_BACK, SS_Timer);
reg [7:0] div;
reg [7:0] tma;
reg [7:0] tima;
reg [2:0] tac;
reg tima_overflow;
reg tima_overflow_1;
reg tima_overflow_2;
reg tima_overflow_3;
reg tima_overflow_4;
// Unused legacy bits: 8-9, 29, 39-41
assign {SS_Timer_BACK[37:30], SS_Timer_BACK[ 7: 0]} = clk_div;
assign SS_Timer_BACK[17:10] = tima;
assign SS_Timer_BACK[25:18] = tma;
assign SS_Timer_BACK[28:26] = tac;
assign SS_Timer_BACK[38] = clk_tac_r;
assign SS_Timer_BACK[46:42] = tima_overflow_buffer;
assign SS_Timer_BACK[ 9: 0] = clk_div;
assign SS_Timer_BACK[17:10] = tima;
assign SS_Timer_BACK[25:18] = tma;
assign SS_Timer_BACK[28:26] = tac;
assign SS_Timer_BACK[29] = irq;
assign SS_Timer_BACK[37:30] = div;
assign SS_Timer_BACK[38] = clk_div_1_9;
assign SS_Timer_BACK[39] = clk_div_1_3;
assign SS_Timer_BACK[40] = clk_div_1_5;
assign SS_Timer_BACK[41] = clk_div_1_7;
assign SS_Timer_BACK[42] = tima_overflow;
assign SS_Timer_BACK[43] = tima_overflow_1;
assign SS_Timer_BACK[44] = tima_overflow_2;
assign SS_Timer_BACK[45] = tima_overflow_3;
assign SS_Timer_BACK[46] = tima_overflow_4;
reg [15:0] clk_div;
wire [7:0] div = clk_div[15:8];
always @(posedge clk_sys) begin
if(reset) begin
tima <= SS_Timer[17:10]; // 0
tma <= SS_Timer[25:18]; // 0
tac <= SS_Timer[28:26]; // 0
irq <= SS_Timer[29]; // 0
div <= SS_Timer[37:30]; // 0
clk_div_1_9 <= SS_Timer[38]; // 0
clk_div_1_3 <= SS_Timer[39]; // 0
clk_div_1_5 <= SS_Timer[40]; // 0
clk_div_1_7 <= SS_Timer[41]; // 0
tima_overflow <= SS_Timer[42]; // 0
tima_overflow_1 <= SS_Timer[43]; // 0
tima_overflow_2 <= SS_Timer[44]; // 0
tima_overflow_3 <= SS_Timer[45]; // 0
tima_overflow_4 <= SS_Timer[46]; // 0
end else if (ce) begin
irq <= 1'b0;
always @(posedge clk_sys) begin : CLK_DIV_BLK
if (reset)
clk_div <= {SS_Timer[37:30], SS_Timer[7:0]}; // 16'd8;
else if(cpu_sel && cpu_wr && (cpu_addr == 2'b00)) // Writing any value to DIV register clears counter.
clk_div <= 16'd2; // For some reason this needs to be set to 2, rather than zero. This differs from sameboy.
else if (ce)
clk_div <= clk_div + 16'd1;
end
tima_overflow <= 1'b0;
tima_overflow_1 <= tima_overflow;
tima_overflow_2 <= tima_overflow_1;
tima_overflow_3 <= tima_overflow_2;
tima_overflow_4 <= tima_overflow_3;
reg [7:0] tma;
if(clk_div[7:0] == 0) // 16kHz
div <= div + 8'd1;
clk_div_1_9 <= clk_div[9];
clk_div_1_3 <= clk_div[3];
clk_div_1_5 <= clk_div[5];
clk_div_1_7 <= clk_div[7];
// timer enabled?
if(tac[2]) begin
// timer frequency, count up when uppermost clk_div bit switches from 1 to 0
if(((tac[1:0] == 2'b00) && (!clk_div[9] && clk_div_1_9)) || // 4 khz
((tac[1:0] == 2'b01) && (!clk_div[3] && clk_div_1_3)) || // 262 khz
((tac[1:0] == 2'b10) && (!clk_div[5] && clk_div_1_5)) || // 65 khz
((tac[1:0] == 2'b11) && (!clk_div[7] && clk_div_1_7))) begin // 16 khz
tima <= tima + 8'd1;
if(tima == 8'hff) begin
tima_overflow <= 1'b1;
end
end
end
if (tima_overflow_3) begin // 1 cycle = 4 clock cycles after timer overflows
irq <= 1'b1; // irq
tima <= tma; // reload timer
end
if (tima_overflow_4) begin // delay 4 is required because cpu write takes place 1 clock cycle later(too late?)
if(cpu_sel && cpu_wr && cpu_addr == 2'b10) begin // writing tma when loading tima has instant effect
tima <= cpu_di;
end
end
if(cpu_sel && cpu_wr) begin
case(cpu_addr)
2'b00: div <= 8'h00; // writing clears counter
2'b01: begin
if (!tima_overflow_4) begin
tima <= cpu_di;
end
tima_overflow_1 <= 1'b0; // prevent irq and loading of tima with write to tima at the same time
end
2'b10: tma <= cpu_di;
2'b11: tac <= cpu_di[2:0];
endcase
always @(posedge clk_sys) begin : TMA_BLK
if (reset)
tma <= SS_Timer[25:18]; // 0
else if (ce) begin
if (cpu_sel && cpu_wr && (cpu_addr == 2'b10))
tma <= cpu_di;
end
end
end
assign cpu_do =
(cpu_addr == 2'b00)?div:
(cpu_addr == 2'b01)?tima:
(cpu_addr == 2'b10)?tma:
{5'b11111, tac};
endmodule
reg [2:0] tac;
// Disabling TAC can create a clock event to TIMA
wire clk_tac = tac[2] && (
(tac[1:0] == 2'b00) ? clk_div[9]:
(tac[1:0] == 2'b01) ? clk_div[3]:
(tac[1:0] == 2'b10) ? clk_div[5]:
clk_div[7]
);
reg clk_tac_r;
always @(posedge clk_sys) begin : TAC_BLK
if (reset) begin
tac <= SS_Timer[28:26]; // 0
clk_tac_r <= SS_Timer[38]; // 0
end else if (ce) begin
clk_tac_r <= clk_tac;
if (cpu_sel && cpu_wr && (cpu_addr == 2'b11))
tac <= cpu_di[2:0];
end
end
/* Overflow timing explanation (https://gbdev.io/pandocs/Timer_Obscure_Behaviour.html#timer-overflow-behaviour)
Here, the timer clock operates at 1/4 of the system clock frequency. That is, the timer aligns with a machine cycle.
Basic sequence of events with values at each cycle:
Clock tick -1: TIMA overflow occurs, i.e. {OVERFLOW_FLAG, TIMA} == (8'hff + 1)
Overflow cycle:
Clock tick 0: OVERFLOW_BUFFER[0] = 1, TIMA = 0, IRQ = 0,
Clock tick 1: OVERFLOW_BUFFER[1] = 1, TIMA = 0, IRQ = 0,
Clock tick 2: OVERFLOW_BUFFER[2] = 1, TIMA = 0, IRQ = 0,
Clock tick 3: OVERFLOW_BUFFER[3] = 1, TIMA = 0, IRQ = 0,
Interrupt Cycle:
Clock tick 4: OVERFLOW_BUFFER[4] = 1, TIMA = 1?, IRQ = 1,
Clock tick 5: OVERFLOW_BUFFER = X, TIMA = TMA, IRQ = 0,
If TIMA is written to during the overflow cycle (ticks 0 to 3) the IRQ is prevented and the timer continues as normal.
If TMA is written at the same time TMA is loaded into TIMA (tick 5), the new value is also loaded into TIMA.
*/
reg [7:0] tima;
reg [4:0] tima_overflow_buffer;
assign irq = tima_overflow_buffer[4];
always @(posedge clk_sys) begin : TIMA_BLK
if(reset) begin
tima <= SS_Timer[17:10]; // 0
tima_overflow_buffer <= SS_Timer[46:42]; // 0
end else if (ce) begin
tima_overflow_buffer <= {tima_overflow_buffer[3:0], 1'b0};
if(clk_tac_r && !clk_tac)
{tima_overflow_buffer[0], tima} <= tima + 1'b1;
// IRQ asserted with clock tick 4 (beginning of interrupt cycle), TIMA write takes place 1 clock TICK later
if (irq) begin
tima <= tma;
if(cpu_sel && cpu_wr && cpu_addr == 2'b10) // Writing TMA when loading TIMA has instant effect
tima <= cpu_di;
end
if(cpu_sel && cpu_wr && cpu_addr == 2'b01) begin
tima_overflow_buffer[4:1] <= 4'b0; // Writing to TIMA during overflow cycle prevents interrupt
if (!irq) // Writes to TIMA during interrupt cycle are ignored.
tima <= cpu_di;
end
end
end
endmodule