module counter_timer_high (
    input resetn,
    input clkin,

    input  [3:0]  reg_val_we,
    input  [31:0] reg_val_di,
    output [31:0] reg_val_do,

    input 	  reg_cfg_we,
    input  [31:0] reg_cfg_di,
    output [31:0] reg_cfg_do,

    input  [3:0]  reg_dat_we,
    input  [31:0] reg_dat_di,
    output [31:0] reg_dat_do,
    input	  stop_in,
    input	  enable_in,
    input	  is_offset,
    input	  strobe,
    output	  stop_out,
    output	  enable_out,
    output	  irq_out
);

reg [31:0] value_cur;
reg [31:0] value_reset;
reg	   irq_out;
wire	   enable_in;		// Enable from chained counter
wire	   strobe;		// Count strobe from low word counter
wire	   enable_out;		// Enable to chained counter (sync)
reg	   stop_out;		// Stop signal to low word counter

wire [31:0] value_cur_plus;	// Next value, on up-count
wire [31:0] value_cur_minus;	// Next value, on down-count
wire [31:0] value_check_plus;	// Value to check for stop condition during up count
wire	    loc_enable;		// Local enable

reg enable;	// Enable (start) the counter/timer
reg lastenable;	// Previous state of enable (catch rising/falling edge)
reg oneshot;	// Set oneshot (1) mode or continuous (0) mode
reg updown;	// Count up (1) or down (0)
reg irq_ena;	// Enable interrupt on timeout
reg chain;	// Chain to a secondary timer

// Configuration register

assign reg_cfg_do = {27'd0, irq_ena, chain, updown, oneshot, enable};

always @(posedge clkin or negedge resetn) begin
    if (resetn == 1'b0) begin
	enable <= 1'b0;
	oneshot <= 1'b0;
	updown <= 1'b0;
	chain <= 1'b0;
	irq_ena <= 1'b0;
    end else begin
	if (reg_cfg_we) begin
	    enable <= reg_cfg_di[0];
	    oneshot <= reg_cfg_di[1];
	    updown <= reg_cfg_di[2];
	    chain <= reg_cfg_di[3];
	    irq_ena <= reg_cfg_di[4];
	end
    end
end

// Counter/timer reset value register

assign reg_val_do = value_reset;

always @(posedge clkin or negedge resetn) begin
    if (resetn == 1'b0) begin
	value_reset <= 32'd0;
    end else begin
	if (reg_val_we[3]) value_reset[31:24] <= reg_val_di[31:24];
	if (reg_val_we[2]) value_reset[23:16] <= reg_val_di[23:16];
	if (reg_val_we[1]) value_reset[15:8] <= reg_val_di[15:8];
	if (reg_val_we[0]) value_reset[7:0] <= reg_val_di[7:0];
    end
end

assign reg_dat_do = value_cur;

// Counter/timer current value register and timer implementation

assign value_cur_plus = value_cur + 1;
assign value_cur_minus = value_cur - 1;

assign value_check_plus = (is_offset) ? value_cur_plus : value_cur;

assign enable_out = enable;
assign loc_enable = (chain == 1'b1) ? (enable && enable_in) : enable;

// When acting as the high 32 bit word of a 64-bit chained counter:
//
// It counts when the low 32-bit counter strobes (strobe == 1).
// It sets "stop_out" and stops on the stop condition.

always @(posedge clkin or negedge resetn) begin
    if (resetn == 1'b0) begin
	value_cur <= 32'd0;	
	stop_out <= 1'b0;
	irq_out <= 1'b0;
	lastenable <= 1'b0;
    end else begin
	lastenable <= loc_enable;

	if (reg_dat_we != 4'b0000) begin
	    if (reg_dat_we[3] == 1'b1) value_cur[31:24] <= reg_dat_di[31:24];
	    if (reg_dat_we[2] == 1'b1) value_cur[23:16] <= reg_dat_di[23:16];
	    if (reg_dat_we[1] == 1'b1) value_cur[15:8] <= reg_dat_di[15:8];
	    if (reg_dat_we[0] == 1'b1) value_cur[7:0] <= reg_dat_di[7:0];

	end else if (loc_enable == 1'b1) begin
	    /* IRQ signals one cycle after stop, if IRQ is enabled */
	    irq_out <= (irq_ena) ? stop_out : 1'b0;

	    if (updown == 1'b1) begin
		if (lastenable == 1'b0) begin
		    value_cur <= 32'd0;
		    stop_out <= 1'b0;
		end else if (chain) begin
		    // Chained counter behavior
		    if (value_check_plus == value_reset) begin
		    	stop_out <= 1'b1;
		    end
		    if (stop_in == 1'b1) begin	// lower word counter stopped
			if (oneshot != 1'b1) begin
			    value_cur <= 32'd0;	// reset count
			    stop_out <= 1'b0;	// no longer stopped
			end else if (strobe == 1'b1) begin
			    value_cur <= value_cur_plus;
			end
		    end else if (strobe == 1'b1) begin
			value_cur <= value_cur_plus;
		    end
		end else begin
		    // Standalone counter behavior
		    if (value_cur == value_reset) begin
		    	if (oneshot != 1'b1) begin
			    value_cur <= 32'd0;
		    	    stop_out <= 1'b0;
		    	end else begin
		    	    stop_out <= 1'b1;
			end
		    end else begin
		    	if (value_cur_plus == 32'd0) begin
			    stop_out <= 1'b1;
			end else begin
			    stop_out <= 1'b0;
			end
		    	value_cur <= value_cur_plus;	// count up
		    end
		end
	    end else begin
		if (lastenable == 1'b0) begin
		    value_cur <= value_reset;
		    stop_out <= 1'b0;
		end else if (chain) begin
		    // Chained counter behavior
		    if (value_cur == 32'd0) begin
			stop_out <= 1'b1;
		    end
		    if (stop_in == 1'b1) begin	// lower word counter stopped
			if (oneshot != 1'b1) begin
			    value_cur <= value_reset;	// reset count
			    stop_out <= 1'b0;		// no longer stopped
			end
		    end else if (strobe == 1'b1) begin
		    	value_cur <= value_cur_minus;	// count down
		    end
		end else begin
		    // Standalone counter behavior
		    if (value_cur == 32'd0) begin
		    	if (oneshot != 1'b1) begin
			    value_cur <= value_reset;
			    stop_out <= 1'b0;
			end else begin
			    stop_out <= 1'b1;
			end
		    end else begin
		    	if (value_cur_minus == 32'd0) begin
			    stop_out <= 1'b1;
			end else begin
			    stop_out <= 1'b0;
			end
		    	value_cur <= value_cur_minus;	// count down
		    end
		end
	    end
	end else begin
	    stop_out <= 1'b0;
	end
    end
end

endmodule