//
// Copyright 2011 Ettus Research LLC
//
// This program 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 program 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/>.
//



module time_sync
  (input wb_clk_i, input rst_i,
   input cyc_i, input stb_i, input [2:0] adr_i,
   input we_i, input [31:0] dat_i, output [31:0] dat_o, output ack_o,
   input sys_clk_i, output [31:0] master_time_o,
   input pps_posedge, input pps_negedge, 
   input exp_pps_in, output exp_pps_out,
   output reg int_o,
   output reg epoch_o,
   output reg pps_o );
   
   wire [31:0] master_time_rcvd;
   reg [31:0]  master_time;
   reg [31:0]  delta_time;

   reg 	       internal_tick;
   wire        sync_rcvd, pps_ext;
   reg [31:0]  tick_time, tick_time_wb;
   wire        tick_free_run;
   reg 	       tick_int_enable, tick_source, external_sync;
   reg [31:0]  tick_interval;
   reg 	       sync_on_next_pps;
   reg         sync_every_pps;
   reg 	       pps_edge;
   
   // Generate master time
   always @(posedge sys_clk_i)
     if(rst_i)
       master_time <= 0;
     else if(external_sync & sync_rcvd)
       master_time <= master_time_rcvd + delta_time;
     else if(pps_ext & (sync_on_next_pps|sync_every_pps))
       master_time <= 0;
     else
       master_time <= master_time + 1;
   assign      master_time_o = master_time;
   
   time_sender time_sender
     (.clk(sys_clk_i),.rst(rst_i),
      .master_time(master_time),
      .send_sync(internal_tick),
      .exp_pps_out(exp_pps_out) );

   time_receiver time_receiver
     (.clk(sys_clk_i),.rst(rst_i),
      .master_time(master_time_rcvd),
      .sync_rcvd(sync_rcvd),
      .exp_pps_in(exp_pps_in) );

   assign     ack_o = stb_i;
   wire       wb_write = cyc_i & stb_i & we_i;
   wire       wb_read = cyc_i & stb_i & ~we_i;
   wire       wb_acc = cyc_i & stb_i;
   
   always @(posedge wb_clk_i)
     if(rst_i)
       begin
	  tick_source <= 0;
	  tick_int_enable <= 0;
	  external_sync <= 0;
	  tick_interval <= 100000-1;  // default to 1K times per second
	  delta_time <= 0;
	  pps_edge <= 0;
	  sync_every_pps <= 0;
       end
     else if(wb_write)
       case(adr_i[2:0])
	 3'd0 :
	   begin
	      tick_source <= dat_i[0];
	      tick_int_enable <= dat_i[1];
	      external_sync <= dat_i[2];
	      pps_edge <= dat_i[3];
	      sync_every_pps <= dat_i[4];
	   end
	 3'd1 :
	   tick_interval <= dat_i;
	 3'd2 :
	   delta_time <= dat_i;
	 3'd3 :
	   ;
	 // Do nothing here, this is to arm the sync_on_next
       endcase // case(adr_i[2:0])

   always @(posedge sys_clk_i)
     if(rst_i)
       sync_on_next_pps <= 0;
     else if(pps_ext)
       sync_on_next_pps <= 0;
     else if(wb_write & (adr_i[2:0] == 3))
       sync_on_next_pps <= 1;
   
   always @(posedge sys_clk_i)
     if(internal_tick)
       tick_time <= master_time;

   always @(posedge wb_clk_i)
     tick_time_wb <= tick_time;
   
   assign dat_o = tick_time_wb;

   always @(posedge sys_clk_i)
     internal_tick <= (tick_source == 0) ? tick_free_run : pps_ext;

   reg [31:0] counter;
   always @(posedge sys_clk_i)
     if(rst_i)
       counter <= 0;
     else if(tick_free_run)
       counter <= 0;
     else
       counter <= counter + 1;
   assign     tick_free_run = (counter >= tick_interval);
   
   // Properly Latch and edge detect External PPS input
   reg 	      pps_in_d1, pps_in_d2;
   always @(posedge sys_clk_i)
     begin
	pps_in_d1 <= pps_edge ? pps_posedge : pps_negedge;
	pps_in_d2 <= pps_in_d1;
     end
   assign pps_ext = pps_in_d1 & ~pps_in_d2;

   always @(posedge sys_clk_i)
     pps_o <= pps_ext;
   
   // Need to register this?
   reg 	  internal_tick_d1;
   always @(posedge sys_clk_i) internal_tick_d1 <= internal_tick;
   
   always @(posedge wb_clk_i)
     if(rst_i)
       int_o <= 0;
     else if(tick_int_enable & (internal_tick | internal_tick_d1))
       int_o <= 1;
     else
       int_o <= 0;

   always @(posedge sys_clk_i)
     if(rst_i)
       epoch_o <= 0;
     else
       epoch_o <= (master_time_o[27:0] == 0);
endmodule // time_sync
