mirror of
https://github.com/MiSTer-devel/Gameboy_MiSTer.git
synced 2026-05-17 03:03:43 +00:00
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:
@@ -269,7 +269,6 @@ localparam CONF_STR = {
|
||||
"P3OR,Rewind Capture,Off,On;",
|
||||
"P3-;",
|
||||
"P3o3,Super Game Boy + GBC,Off,On;",
|
||||
|
||||
|
||||
"-;",
|
||||
"R0,Reset;",
|
||||
|
||||
@@ -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
|
||||
(
|
||||
|
||||
20
rtl/gb.v
20
rtl/gb.v
@@ -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 ),
|
||||
|
||||
136
rtl/gbc_snd.vhd
136
rtl/gbc_snd.vhd
@@ -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";
|
||||
|
||||
287
rtl/timer.v
287
rtl/timer.v
@@ -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
|
||||
Reference in New Issue
Block a user