module counter_timer_low (
    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,
    output	  strobe,
    output	  enable_out,
    output	  stop_out,
    output	  is_offset,
    output	  irq_out
);

reg [31:0] value_cur;
reg [31:0] value_reset;
reg	   irq_out;
wire	   stop_in;		// High 32 bits counter has stopped
reg	   strobe;		// Strobe to high 32 bits counter; occurs
				// one cycle before actual timeout and
				// irq signal.
reg	   stop_out;		// Stop condition flag

wire [31:0] value_cur_plus;	// Next value, on up-count
wire [31:0] value_cur_minus;	// Next value, on down-count
wire	    is_offset;
wire	    loc_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 loc_enable = (chain == 1'b1) ? (enable && enable_in) : enable;
assign enable_out = enable;

// If counting up and the stop condition on the low 32 bits is zero,
// then signal to the high word counter that the stop condition of the
// high word must be adjusted by one, since it will roll over at the same
// time and must be signaled early.

assign is_offset = ((updown == 1'b1) && (value_reset == 0));

// When acting as low 32-bit word of a 64-bit chained counter:
// It sets the output strobe on the stop condition, one cycle early.
// It stops on the stop condition if "stop_in" is high.

always @(posedge clkin or negedge resetn) begin
    if (resetn == 1'b0) begin
	value_cur <= 32'd0;	
	strobe <= 1'b0;
	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_out, 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;
		    strobe <= 1'b0;
		    stop_out <= 1'b0;
		end else if (chain == 1'b1) begin
		    // Rollover strobe (2 clock cycles advanced)
		    if (value_cur == -1) begin
			strobe <= 1'b1;
		    end else begin
			strobe <= 1'b0;
		    end

		    // Chained counter behavior
		    if ((stop_in == 1'b1) && (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 ((stop_in == 1'b1) && (value_cur_plus == value_reset)) begin
		    	    stop_out <= 1'b1;
			end else begin
		    	    stop_out <= 1'b0;
			end
		    	value_cur <= value_cur_plus;	// count up
		    end
		end else begin

		    // Single 32-bit 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 == value_reset) 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;
		    strobe <= 1'b0;
		end else if (chain == 1'b1) begin
		    // Rollover strobe (2 clock cycles advanced)
		    if (value_cur == 2) begin
			strobe <= 1'b1;
		    end else begin
			strobe <= 1'b0;
		    end

		    // Chained counter behavior
		    if ((stop_in == 1'b1) && (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 ((stop_in == 1'b1) && (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 else begin

		    // Single 32-bit 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
	    strobe <= 1'b0;
	end
    end
end

endmodule