mirror of
https://github.com/MiSTer-devel/Gameboy_MiSTer.git
synced 2026-04-19 03:04:09 +00:00
* 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
152 lines
4.8 KiB
Verilog
152 lines
4.8 KiB
Verilog
// Implementation follows the gbdev pandocs
|
|
// https://gbdev.io/pandocs/Timer_Obscure_Behaviour.html
|
|
|
|
|
|
module timer (
|
|
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,
|
|
output [7:0] cpu_do,
|
|
output apu_framecount_en,
|
|
|
|
// 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};
|
|
|
|
// 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;
|
|
|
|
reg clk_sound_r;
|
|
wire clk_sound = cpu_speed ? div[5] : div[4];
|
|
|
|
// 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
|
|
|
|
// Save states
|
|
wire [46:0] SS_Timer;
|
|
wire [46:0] SS_Timer_BACK;
|
|
|
|
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);
|
|
|
|
// 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;
|
|
|
|
reg [15:0] clk_div;
|
|
wire [7:0] div = clk_div[15:8];
|
|
|
|
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
|
|
|
|
reg [7:0] tma;
|
|
|
|
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
|
|
|
|
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 |