// de_monitor.v
//
// Monitors DE timing on the pixel-clock side (RxCLKOUT, ~72 MHz).
//
//   - Counts DE rising edges per frame.
//   - Counts pixel-clock cycles per DE active interval (line width).
//   - Frame boundary is declared when DE has been idle longer than one
//     line period (the gap between the last active line and the first
//     line of the next frame is the vertical blanking interval, which is
//     many line periods long; any intra-frame gap is shorter than one
//     line period).
//   - Flags an anomaly if the just-finished frame had lines != EXPECTED_LINES,
//     or if any line in the frame had width != EXPECTED_WIDTH.
//
// Outputs on `frame_done` (1 pix_clk pulse):
//   lines_o     - lines counted in the frame that just ended
//   width_o     - width of the offending line if any width was wrong,
//                 otherwise the width of the last line in the frame
//   anomaly_o   - 1 if lines_o != EXPECTED_LINES or any width was wrong
//
// Assumes DE is active-high (DS90CF386 default for the Ampire panel).

module de_monitor #(
    parameter [15:0] EXPECTED_LINES = 16'd800,
    parameter [15:0] EXPECTED_WIDTH = 16'd1280,
    // Idle-gap threshold for declaring "end of frame". Must be larger
    // than one full line period in pixel clocks (~1432 for this panel)
    // and smaller than the vertical blanking interval (~54k pixels).
    parameter [15:0] FRAME_GAP_PIX  = 16'd2048
)(
    input  wire        pix_clk,
    input  wire        rst_n,
    input  wire        de,

    output reg         frame_done,    // 1 pix_clk pulse
    output reg  [15:0] lines_o,
    output reg  [15:0] width_o,
    output reg         anomaly_o
);

    reg de_q;
    wire de_rise = de & ~de_q;
    wire de_fall = ~de & de_q;

    reg [15:0] line_width;     // running width of current DE-high interval
    reg [15:0] last_width;     // width of most recent completed line
    reg [15:0] bad_width;      // sticky: width of first wrong-width line in frame
    reg        any_bad_width;  // sticky: at least one wrong-width line this frame

    reg [15:0] line_count;     // lines seen so far in current frame
    reg [15:0] gap_count;      // pixel-clocks since DE last fell
    reg        frame_active;   // we have seen at least one DE this frame

    always @(posedge pix_clk or negedge rst_n) begin
        if (!rst_n) begin
            de_q          <= 1'b0;
            line_width    <= 16'd0;
            last_width    <= 16'd0;
            bad_width     <= 16'd0;
            any_bad_width <= 1'b0;
            line_count    <= 16'd0;
            gap_count     <= 16'd0;
            frame_active  <= 1'b0;
            frame_done    <= 1'b0;
            lines_o       <= 16'd0;
            width_o       <= 16'd0;
            anomaly_o     <= 1'b0;
        end else begin
            de_q       <= de;
            frame_done <= 1'b0;

            // --- per-line width measurement ---
            if (de_rise) begin
                line_width <= 16'd1;
            end else if (de) begin
                line_width <= line_width + 16'd1;
            end

            if (de_fall) begin
                last_width <= line_width;
                if (line_width != EXPECTED_WIDTH && !any_bad_width) begin
                    bad_width     <= line_width;
                    any_bad_width <= 1'b1;
                end
            end

            // --- line counting & frame-boundary detection ---
            if (de_rise) begin
                line_count   <= line_count + 16'd1;
                frame_active <= 1'b1;
                gap_count    <= 16'd0;
            end else if (!de) begin
                if (gap_count != 16'hFFFF) gap_count <= gap_count + 16'd1;

                // End of frame: long DE-idle after at least one line.
                if (frame_active && gap_count == FRAME_GAP_PIX) begin
                    frame_done <= 1'b1;
                    lines_o    <= line_count;
                    width_o    <= any_bad_width ? bad_width : last_width;
                    anomaly_o  <= (line_count != EXPECTED_LINES) || any_bad_width;

                    // reset frame state
                    line_count    <= 16'd0;
                    gap_count     <= 16'd0;
                    any_bad_width <= 1'b0;
                    bad_width     <= 16'd0;
                    frame_active  <= 1'b0;
                end
            end
        end
    end

endmodule
