diff --git a/Gameboy.sv b/Gameboy.sv index befce35..fe97317 100644 --- a/Gameboy.sv +++ b/Gameboy.sv @@ -269,7 +269,6 @@ localparam CONF_STR = { "P3OR,Rewind Capture,Off,On;", "P3-;", "P3o3,Super Game Boy + GBC,Off,On;", - "-;", "R0,Reset;", diff --git a/rtl/bus_savestates.vhd b/rtl/bus_savestates.vhd index db5c7ae..f8feeb7 100644 --- a/rtl/bus_savestates.vhd +++ b/rtl/bus_savestates.vhd @@ -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 ( diff --git a/rtl/gb.v b/rtl/gb.v index 75621dd..e600582 100644 --- a/rtl/gb.v +++ b/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 ), diff --git a/rtl/gbc_snd.vhd b/rtl/gbc_snd.vhd index 3f770f9..340bf7c 100644 --- a/rtl/gbc_snd.vhd +++ b/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"; diff --git a/rtl/timer.v b/rtl/timer.v index ab6ecfa..593b41d 100644 --- a/rtl/timer.v +++ b/rtl/timer.v @@ -1,183 +1,152 @@ -// -// timer.v -// -// Gameboy for the MIST board https://github.com/mist-devel -// -// Copyright (c) 2015 Till Harbaum -// -// 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 . -// +// 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 \ No newline at end of file