+`define ADDR_DIV 16'hFF04
+`define ADDR_TIMA 16'hFF05
+`define ADDR_TMA 16'hFF06
+`define ADDR_TAC 16'hFF07
+
+module Timer(
+ input clk,
+ input wr,
+ input rd,
+ input [15:0] addr,
+ inout [7:0] data,
+ output reg irq);
+
+ reg [7:0] tima = 0, tma = 0, tac = 0, div = 0;
+ reg ovf = 0;
+ reg [9:0] clkdv;
+
+ wire is_tima = addr == `ADDR_TIMA;
+ wire is_tma = addr == `ADDR_TMA;
+ wire is_tac = addr == `ADDR_TAC;
+
+ assign data = rd ?
+ is_tima ? tima :
+ is_tma ? tma :
+ is_tac ? tac :
+ 8'bzzzzzzzz :
+ 8'bzzzzzzzz;
+
+ wire cksel = tac[2] ?
+ (tac[1:0] == 2'b00) ? (clkdv == 10'b0) :
+ (tac[1:0] == 2'b01) ? (clkdv[3:0] == 4'b0) :
+ (tac[1:0] == 2'b10) ? (clkdv[5:0] == 6'b0) :
+ (clkdv[7:0] == 8'b0) :
+ 0;
+
+ always @ (negedge clk)
+ begin
+ if(wr) begin
+ case(addr)
+ `ADDR_DIV: div <= 8'b0;
+ `ADDR_TIMA: tima <= data;
+ `ADDR_TMA: tma <= data;
+ `ADDR_TAC: tac <= data;
+ endcase
+ end
+ else begin
+ if(ovf) begin
+ tima <= tma;
+ ovf <= 0;
+ irq <= 1;
+ end
+ else begin
+ if(cksel)
+ {ovf,tima} <= {1'b0,tima} + 1;
+ if(irq)
+ irq <= 0;
+ end
+
+ if(clkdv[7:0] == 8'b0)
+ div <= div + 1;
+ end
+ clkdv <= clkdv + 1;
+ end
+
+endmodule