//
// sysclock.sv
//
// Very simple unit that keeps track of time in "human" format based
// on 32.768 kHz signal from the RTC. The registers have to be set from
// software, presumably from reading the RTC.
//
// Register 0 contains the 2 s granular date and time in FAT filesystem format.
// Register 1 contains two copies of a 16-bit 32 kHz counter:
// the upper half contains the current counter, and the lower half is
// a holding register updated when register 0 is read, and, if written
// to in adcance, will write the counter when register 0 is written.

module sysclock (
		 input		   rst_n,
		 input		   sys_clk,
		 input		   rtc_clk,

		 input		   valid,
		 input		   addr,
		 output reg [31:0] rdata,
		 input [31:0]	   wdata,
		 input [3:0]	   wstrb,

		 output		   periodic
		);

   parameter PERIODIC_HZ_LG2 = 5;

   wire		rtc_clk_sync;
   reg		rtc_clk_q;
   reg		rtc_clk_stb;

   synchronizer rtc_sync (
			  .rst_n ( 1'b1 ),
			  .clk ( sys_clk ),
			  .d ( rtc_clk ),
			  .q ( rtc_clk_sync )
			  );

   always @(posedge sys_clk)
     begin
	rtc_clk_q <= rtc_clk_sync;
	rtc_clk_stb <= rtc_clk_sync & ~rtc_clk_q;
     end

   function logic [4:0] maxday(input [3:0] mon,
			       input [6:0] year);
      case (mon)
	4'd4,			// April
	4'd6,			// June
	4'd9,			// September
	4'd11: begin		// November
	   maxday = 5'd30;
	end
	4'd2: begin		// February
	   if ((|year[1:0]) | (year == (2100 - 1980)))
	     maxday = 5'd28;
	   else
	     maxday = 5'd29;
	end
	default: begin
	   maxday = 5'd31;
	end
	endcase // case (mon)
   endfunction // mdays

   function logic [7:0] tick(input [7:0] me,
			     input [7:0] start,
			     input	 wrap_pre,
			     input	 wrap_me);

      tick = wrap_me ? start : me + wrap_pre;
   endfunction // tick

   // Counter read/writes holding register
   reg [15:0] tm_hold;
   reg [ 1:0] tm_whold;		// Byte enables for hold register

   reg [15:0] tm_tick;
   reg [31:0] tm_dt;		// Day and time in FAT filesystem format

   wire [4:0] tm_2sec = tm_dt[4:0];
   wire [5:0] tm_min  = tm_dt[10:5];
   wire [4:0] tm_hour = tm_dt[15:11];

   wire [4:0] tm_mday = tm_dt[20:16];
   wire [3:0] tm_mon  = tm_dt[24:21];
   wire [6:0] tm_year = tm_dt[31:25];

   wire wrap_tick = rtc_clk_stb & &tm_tick;
   wire wrap_sec  = wrap_tick & (tm_2sec >= 5'd29);
   wire wrap_min  = wrap_sec  & (tm_min  >= 6'd59);
   wire wrap_hour = wrap_min  & (tm_hour >= 5'd23);
   wire wrap_mday = wrap_hour & (tm_mday >= maxday(tm_mon, tm_year));
   wire wrap_mon  = wrap_mday & (tm_mon  >= 4'd12);

   // Yes, it may jump if the counter is written...
   assign periodic = tm_tick[14 - PERIODIC_HZ_LG2];

   always @(posedge sys_clk)
     begin
	tm_tick      <= tm_tick + rtc_clk_stb;
	tm_dt[4:0]   <= tick(tm_2sec, 5'd0, wrap_tick, wrap_sec);
	tm_dt[10:5]  <= tick(tm_min,  6'd0, wrap_sec,  wrap_min);
	tm_dt[15:11] <= tick(tm_hour, 4'd0, wrap_min,  wrap_hour);
	tm_dt[20:16] <= tick(tm_mday, 5'd1, wrap_hour, wrap_mday);
	tm_dt[24:21] <= tick(tm_mon,  4'd1, wrap_mday, wrap_mon);
	tm_dt[31:25] <= tick(tm_year, 7'hxx, wrap_mon,  1'b0);

	if (~rst_n)
	  begin
	     tm_hold <= tm_tick;
	     tm_whold <= 2'b00;
	  end
	else if (valid)
	  case (addr)
	    1'b0: begin
	       // Datetime register
	       if (wstrb[0]) tm_dt[7:0]   <= wdata[7:0];
	       if (wstrb[1]) tm_dt[15:8]  <= wdata[15:8];
	       if (wstrb[2]) tm_dt[23:16] <= wdata[23:16];
	       if (wstrb[3]) tm_dt[31:24] <= wdata[31:24];

	       if (tm_whold[0]) tm_tick[7:0]  <= tm_hold[7:0];
	       if (tm_whold[1]) tm_tick[15:8] <= tm_hold[15:8];
	       tm_hold  <= tm_tick;
	       tm_whold <= 2'b00;
	    end // case: 1'b0
	    1'b1: begin
	       // Tick register
	       if (wstrb[0]) tm_hold[7:0]  <= wdata[7:0];
	       if (wstrb[1]) tm_hold[15:8] <= wdata[15:8];
	       if (wstrb[2]) tm_tick[7:0]  <= wdata[23:16];
	       if (wstrb[3]) tm_tick[15:8] <= wdata[31:24];
	       tm_whold <= tm_whold | wstrb[1:0];
	    end
	  endcase // case (addr)
     end // always @ (posedge sys_clk)

   // Read data MUX
   always @(*)
     case (addr)
       1'b0: rdata = tm_dt;
       1'b1: rdata = { tm_tick, tm_hold };
     endcase // case (addr)

endmodule // sysclock
