`resetall
`timescale 1ns / 1ps
`default_nettype none

module timing_engine #
(
    parameter integer AXI_ADDR_WIDTH	= 32,
    parameter integer AXI_DATA_WIDTH	= 32,    
    parameter NUM_RX	= 2,
    parameter NUM_TIMING_PULSES	= 8     
)
(
    input  wire         clk,
    input  wire         pps,
   
    // AXI4L Config Interface
    axi4l_intf.slave    ctrl_if,

    output wire         start_of_cpi,
    output wire         start_of_pulse,
    
    output wire [NUM_TIMING_PULSES-1:0]   timing_pulses

    // axi4s_intf.master   hdr_out[NUM_RX]
);

// ------------------------------
// AXIL Decode
// ------------------------------
wire [AXI_ADDR_WIDTH-1 : 0]		raddr;
wire [AXI_ADDR_WIDTH-1 : 0]		waddr;
wire 						    rden;
wire 						    wren;
wire [AXI_DATA_WIDTH-1 : 0]		wdata;
reg [AXI_DATA_WIDTH-1 : 0]		rdata;


axil_slave 
# (
	.DATA_WIDTH(AXI_DATA_WIDTH),
	.ADDR_WIDTH(AXI_ADDR_WIDTH)
) axil_slave_i 
(
	// AXIL Slave
	.S_AXI_ACLK(ctrl_if.clk),
	.S_AXI_ARESETN(ctrl_if.resetn),
	.S_AXI_AWADDR(ctrl_if.awaddr),
	.S_AXI_AWPROT(ctrl_if.awprot),
	.S_AXI_AWVALID(ctrl_if.awvalid),
	.S_AXI_AWREADY(ctrl_if.awready),
	.S_AXI_WDATA(ctrl_if.wdata),
	.S_AXI_WSTRB(ctrl_if.wstrb),
	.S_AXI_WVALID(ctrl_if.wvalid),
	.S_AXI_WREADY(ctrl_if.wready),
	.S_AXI_BRESP(ctrl_if.bresp),
	.S_AXI_BVALID(ctrl_if.bvalid),
	.S_AXI_BREADY(ctrl_if.bready),
	.S_AXI_ARADDR(ctrl_if.araddr),
	.S_AXI_ARPROT(ctrl_if.arprot),
	.S_AXI_ARVALID(ctrl_if.arvalid),
	.S_AXI_ARREADY(ctrl_if.arready),
	.S_AXI_RDATA(ctrl_if.rdata),
	.S_AXI_RRESP(ctrl_if.rresp),
	.S_AXI_RVALID(ctrl_if.rvalid),
	.S_AXI_RREADY(ctrl_if.rready),	
	
	.raddr(raddr),
	.waddr(waddr),
	.wren(wren),
	.rden(rden),
	.wdata(wdata),
	.rdata(rdata)
);

// ------------------------------
// Config Registers
// ------------------------------
wire reset;
assign reset = ~ctrl_if.resetn;
reg [31:0] reg_ctrl;
reg [27:0] reg_pri;
reg [27:0] reg_num_pulses; 
reg [27:0] reg_inter_cpi; 
reg [31:0] reg_pps_sec_set;
reg [27:0] reg_pulse_width [NUM_TIMING_PULSES-1:0];
reg [28:0] reg_pulse_start [NUM_TIMING_PULSES-1:0];

reg [63:0] system_time;
reg [63:0] pps_frac_sec;
reg [31:0] pps_sec;
reg reg_pps_set;
reg hdr_bram_we;

always @ (posedge ctrl_if.clk) begin
    if (reset) begin
        reg_ctrl <= 0;
    end else if (wren && waddr[11:0] == 'h000) begin
        reg_ctrl <= wdata;
    end
end

always @ (posedge ctrl_if.clk) begin
    if (reset) begin
        reg_pri <= 0;
    end else if (wren && waddr[11:0] == 'h004) begin
        reg_pri <= wdata;
    end
end

always @ (posedge ctrl_if.clk) begin
    if (reset) begin
        reg_num_pulses <= 0;
    end else if (wren && waddr[11:0] == 'h008) begin
        reg_num_pulses <= wdata;
    end
end

always @ (posedge ctrl_if.clk) begin
    if (reset) begin
        reg_inter_cpi <= 0;
    end else if (wren && waddr[11:0] == 'h010) begin
        reg_inter_cpi <= wdata;
    end
end

always @ (posedge ctrl_if.clk) begin
    if (reset) begin
        reg_pps_sec_set <= 0;
        reg_pps_set <= 0;
    end else if (wren && waddr[11:0] == 'h014) begin
        reg_pps_sec_set <= wdata;
        reg_pps_set <= 1;
    end else begin
        reg_pps_set <= 0;
    end
end

genvar gen_reg;
generate
    for (gen_reg = 0; gen_reg < NUM_TIMING_PULSES; gen_reg = gen_reg + 1) begin
        always @ (posedge ctrl_if.clk) begin
            if (reset) begin
                reg_pulse_start[gen_reg] <= 28'hFFFFFF;
            end else if (wren && waddr[11:0] == ('h080 + gen_reg*8)) begin
                reg_pulse_start[gen_reg] <= wdata;
            end
        end
        
        always @ (posedge ctrl_if.clk) begin
            if (reset) begin
                reg_pulse_width[gen_reg] <= 0;
            end else if (wren && waddr[11:0] == ('h080 + gen_reg*8 + 4)) begin
                reg_pulse_width[gen_reg] <= wdata;
            end
        end
                
    end
endgenerate        



always @ (posedge ctrl_if.clk) begin
    if (wren && waddr[11:10] == 2'b11) begin
        hdr_bram_we <= 1;
    end else begin
        hdr_bram_we <= 0;
    end
end

always @ (posedge ctrl_if.clk) begin
    if (rden) begin
        if (raddr[11:0] == 'h000)
            rdata <= reg_ctrl;
        if (raddr[11:0] == 'h004)
            rdata <= reg_pri;
        if (raddr[11:0] == 'h008)
            rdata <= reg_num_pulses;        
        if (raddr[11:0] == 'h00C)
            rdata <= reg_inter_cpi;           
    end
end

// ------------------------------
// Timestamping
// ------------------------------
reg [63:0] system_time_start_of_cpi;
reg [63:0] pps_sec_start_of_cpi;
reg [63:0] pps_frac_sec_start_of_cpi;


reg [15:0] pps_pipe;
reg pps_debounce;
reg pps_q;
reg pps_q2;
reg pps_red;

always @ (posedge clk) begin
    pps_pipe <= {pps_pipe[14:0], pps};
    pps_debounce = |pps_pipe;
    pps_q <= pps_debounce;
    pps_q2 <= pps_q;
    pps_red <= !pps_q2 & pps_q;
end

always @ (posedge clk) begin
    system_time <= system_time + 1;

    if (start_of_cpi) begin
        system_time_start_of_cpi <= system_time;
        pps_frac_sec_start_of_cpi <= pps_frac_sec;
        pps_sec_start_of_cpi <= pps_sec;
    end
    
    // Fractional seconds rolls over every time PPS strobes
    if (pps_red) begin
        pps_frac_sec <= 0;
    end else begin
        pps_frac_sec <= pps_frac_sec + 1;
    end
    
    if (reg_pps_set) begin
        pps_sec <= reg_pps_sec_set;
    end else if (pps_red) begin
        pps_sec <= pps_sec + 1;
    end

end

// ------------------------------
// Header Generation
// ------------------------------
// wire start_of_cpi_hdr_clk;

// xpm_cdc_pulse #(
//    .RST_USED(0)
// )
// xpm_cdc_pulse_inst (
//     .src_clk(clk),     
//     .src_pulse(start_of_cpi_stretch), 
//     .dest_clk(hdr_out[0].clk), 
//     .dest_pulse(start_of_cpi_hdr_clk) 
// );

// reg [7:0] hdr_count;
// reg hdr_active;
// reg hdr_active_q;
// reg hdr_active_q2;
// reg hdr_active_q3;
// reg [63:0] hdr_bram_out;
// reg [63:0] hdr_data;
// wire hdr_tlast;
// reg hdr_tlast_q;
// reg hdr_tlast_q2;
// reg hdr_tlast_q3;

// always @ (posedge hdr_out[0].clk) begin
//     if (rst == 1'b1) begin
//         hdr_count <= 0;
//         hdr_active <= 0;
//     end else begin
//         if (start_of_cpi_hdr_clk) begin
//             hdr_active <= 1;
//             hdr_count <= 0;
//         end

//         if (hdr_active) begin
//             hdr_count <= hdr_count + 1;
//             if (hdr_count == 127) begin
//                 hdr_active <= 0;
//             end
//         end

//         hdr_active_q <= hdr_active;
//         hdr_active_q2 <= hdr_active_q;
//         hdr_active_q3 <= hdr_active_q2;

//         hdr_tlast_q <= hdr_tlast;
//         hdr_tlast_q2 <= hdr_tlast_q;        
//         hdr_tlast_q3 <= hdr_tlast_q2;  


//         if (hdr_count == 6) begin  // index 3 of header after accounting for latency (64 bit words)
//             hdr_data <= {32'h00000000, pps_sec_start_of_cpi};
//         end else if (hdr_count == 7) begin
//             hdr_data <= pps_frac_sec_start_of_cpi;          
//         end else if (hdr_count == 8) begin
//             hdr_data <= system_time_start_of_cpi;                   
//         end else begin
//             hdr_data <= hdr_bram_out;
//         end

//     end
// end

// assign hdr_tlast = (hdr_count == 127) ? 1 : 0;

// hdr_mem hdr_mem_i (
//   .clka(ctrl_if.clk),    
//   .ena(1'b1),    
//   .wea(hdr_bram_we),     
//   .addra(waddr[9:2]), 
//   .dina(wdata),   

//   .clkb(hdr_out[0].clk),    
//   .enb(1'b1),    
//   .addrb(hdr_count),
//   .doutb(hdr_bram_out)
// );


// genvar i;
// generate
//     for (i = 0; i < NUM_RX; i = i + 1) begin
//         hdr_fifo hdr_fifo_i (
//             .s_axis_aresetn(rstn), 
//             .s_axis_aclk(hdr_out[i].clk),       
          
//             .s_axis_tvalid(hdr_active_q3),   
//             .s_axis_tready(),   
//             .s_axis_tdata(hdr_data),     
//             .s_axis_tlast(hdr_tlast_q3),     
          
//             .m_axis_tvalid(hdr_out[i].tvalid),   
//             .m_axis_tready(hdr_out[i].tready),   
//             .m_axis_tdata(hdr_out[i].tdata),     
//             .m_axis_tlast(hdr_out[i].tlast)      
//           );
          
//           assign hdr_out[i].tkeep = '1;
//           assign hdr_out[i].tuser = 64;
//           assign hdr_out[i].tdest = 0;
//     end
// endgenerate

// ------------------------------
// Timing
// ------------------------------
wire rst;
wire rstn;

reg [27:0]  pri_cnt;
reg [27:0]  pulse_cnt;
wire inter_cpi_active;
reg start_of_pulse_reg;
reg start_of_cpi_reg;
reg [3:0] start_of_cpi_pipe;
wire start_of_cpi_stretch;

assign rst = reg_ctrl[0];
assign rstn = ~rst;
assign start_of_cpi = start_of_cpi_reg;
assign start_of_pulse = start_of_pulse_reg;

assign inter_cpi_active = (pulse_cnt < reg_num_pulses) ? 0 : 1;
assign start_of_cpi_stretch = |start_of_cpi_pipe;

always @ (posedge clk) begin
    if (rst == 1'b1) begin
        pri_cnt <= 0;
        pulse_cnt <= 0;
        start_of_pulse_reg = 1'b0;
    end else begin

        start_of_cpi_pipe <= {start_of_cpi_pipe[2:0], start_of_cpi};

        start_of_pulse_reg = 1'b0;
        start_of_cpi_reg = 1'b0;
        pri_cnt <= pri_cnt + 1;

        if (inter_cpi_active == 0) begin
            
            if (pri_cnt == 1) begin
                start_of_pulse_reg = 1'b1;
                if (pulse_cnt == 0) begin
                    start_of_cpi_reg = 1'b1;
                end
            end

            if (pri_cnt == reg_pri) begin
                pri_cnt <= 0;
                pulse_cnt <= pulse_cnt + 1;
            end 

        end else begin

            if (pri_cnt == reg_inter_cpi) begin
                pri_cnt <= 0;
                pulse_cnt <= 0;
            end 
        end

    end
end

// ------------------------------
// Pulse Generators
// ------------------------------
reg [NUM_TIMING_PULSES-1:0] pulse_start;
reg [NUM_TIMING_PULSES-1:0] timing_pulses_i;  

genvar j;
generate
    for (j = 0; j < NUM_TIMING_PULSES; j = j + 1) begin
    
        assign timing_pulses[j] =  timing_pulses_i[j] | reg_pulse_start[j][28];
    
        always @ (posedge clk) begin
            if (pri_cnt == reg_pulse_start[j][27:0]) begin
                pulse_start[j] <= 1;
            end else begin  
                pulse_start[j] <= 0;
            end
        end
    
        pulse_generator pulse_generator_i(
            .clk(clk),
            .rst(rst),
            .pulse_length(reg_pulse_width[j]),
            .start_of_pulse(pulse_start[j]),
            .pulse_out(timing_pulses_i[j]),
            .pulse_tlast()
        );

    end
endgenerate

endmodule


`resetall