module PDP_colorlight_top (
    input  wire        clk_25m,
    input  wire        uart_rx,

    // Existing physical controls
    input  wire        cont_button_n,
    input  wire        halt_switch_n,
    input  wire        clear_button_n,
    input  wire        break_enable_switch_n,

    // New full front-panel inputs.
    // All are active-low physical inputs: switch/button closes to GND.
    input  wire [11:0] sw_n,
    input  wire        exam_button_n,
    input  wire        deposit_button_n,
    input  wire        brkset_button_n,
    input  wire        extaddr_button_n,
    input  wire        addrld_button_n,
    input  wire        a7777_button_n,
    input  wire        sstep_button_n,
    input  wire        ma_md_switch_n,

    output wire        uart_tx,
    output wire        led,
    output wire        reader_run_out,
    output wire        disp_rck,
    output wire        disp_sck,
    output wire        disp_dio,

    // External RS-232 interface through MAX3232.
    // TXD/RXD use normal FPGA-side UART logic levels; the MAX3232
    // performs the inversion and level translation to true RS-232.
    output wire        rs232_cts_out_n,
    output wire        rs232_txd_out,
    input  wire        rs232_rxd_in
);

    // ------------------------------------------------------------
    // Physical UART connections
    // ------------------------------------------------------------
    // The historical OTTO core still exposes the old Nexys-style
    // RsTx/RsRx pins.  The TX signal is duplicated both to the
    // Colorlight carrier USB-serial bridge and to the external
    // RS-232 interface.
    //
    // RX is accepted from either input. Both UART inputs idle high;
    // ANDing them allows either the USB bridge or the external RS-232
    // receiver to pull the line low for start/data bits. Do not drive
    // both serial sources at the same time.

    // ------------------------------------------------------------
    // Slow clock for the historical OTTO core
    // ------------------------------------------------------------

    reg [7:0] core_divider = 0;

    always @(posedge clk_25m) begin
        core_divider <= core_divider + 1'b1;
    end

    wire core_clk = core_divider[2];   // x4 test: about 25 MHz / 8 = 3.125 MHz

    // ------------------------------------------------------------
    // Robust front-panel input capture
    // ------------------------------------------------------------
    // Momentary controls are handled in the fixed 25 MHz clock domain,
    // independently of the current PDP core speed.  A detected press is
    // converted into a clean, stretched command level, long enough to be
    // seen by the historical core even if we later change its clock rate.
    //
    // This deliberately does not require the mechanical contact to remain
    // stable for many milliseconds before accepting the press: the first
    // clean sampled closure generates the command, then a release lockout
    // prevents bounce from generating additional commands.
    //
    // Maintained switches are filtered as levels in the same 25 MHz domain.

    wire cont_button_level;
    wire clear_button_level;
    wire brkset_button_level;
    wire addrld_button_level;
    wire a7777_clear_level;
    wire a7777_addrld_level;
    reg  a7777_force_7777_level = 1'b0;
    reg  [31:0] a7777_force_cnt = 32'd0;
    wire clear_command_level;
    wire addrld_command_level;
    wire [11:0] effective_switch_register;
    wire extaddr_button_level;
    wire exam_button_level;
    wire deposit_button_level;

    wire halt_switch;
    wire break_enable_switch;
    wire sstep_button_level;
    wire ma_md_switch_level;

    panel_momentary_command #(
        .PULSE_CYCLES(500000),      // 20 ms at 25 MHz
        .RELEASE_CYCLES(250000)     // 10 ms stable released before re-arming
    ) PANEL_CONT (
        .clk(clk_25m),
        .reset(1'b0),
        .active_raw(~cont_button_n),
        .command_level(cont_button_level)
    );

    panel_momentary_command #(
        .PULSE_CYCLES(500000),
        .RELEASE_CYCLES(250000)
    ) PANEL_CLEAR (
        .clk(clk_25m),
        .reset(1'b0),
        .active_raw(~clear_button_n),
        .command_level(clear_button_level)
    );

    panel_momentary_command #(
        .PULSE_CYCLES(500000),
        .RELEASE_CYCLES(250000)
    ) PANEL_BRKSET (
        .clk(clk_25m),
        .reset(1'b0),
        .active_raw(~brkset_button_n),
        .command_level(brkset_button_level)
    );

    panel_momentary_command #(
        .PULSE_CYCLES(500000),
        .RELEASE_CYCLES(250000)
    ) PANEL_ADDRLD (
        .clk(clk_25m),
        .reset(1'b0),
        .active_raw(~addrld_button_n),
        .command_level(addrld_button_level)
    );

    // A7777 is a convenience command for starting the resident Binary Loader.
    // One press performs a clean CLEAR, then ADDR LOAD 7777.
    // The two phases are deliberately sequenced, not simultaneous, because
    // CLEAR/RESET has priority inside the PDP core.
    panel_a7777_command #(
        .CLEAR_CYCLES(500000),     // 20 ms CLEAR
        .GAP_CYCLES(125000),       // 5 ms reset-release gap
        .ADDRLD_CYCLES(500000),    // 20 ms ADDR LOAD 7777
        .RELEASE_CYCLES(250000)    // 10 ms stable released before re-arming
    ) PANEL_A7777 (
        .clk(clk_25m),
        .reset(1'b0),
        .active_raw(~a7777_button_n),
        .clear_level(a7777_clear_level),
        .addrld_level(a7777_addrld_level)
    );

    panel_momentary_command #(
        .PULSE_CYCLES(500000),
        .RELEASE_CYCLES(250000)
    ) PANEL_MA_MINUS (
        .clk(clk_25m),
        .reset(1'b0),
        .active_raw(~extaddr_button_n),
        .command_level(extaddr_button_level)
    );

    panel_momentary_command #(
        .PULSE_CYCLES(500000),
        .RELEASE_CYCLES(250000)
    ) PANEL_EXAM (
        .clk(clk_25m),
        .reset(1'b0),
        .active_raw(~exam_button_n),
        .command_level(exam_button_level)
    );

    panel_momentary_command #(
        .PULSE_CYCLES(500000),
        .RELEASE_CYCLES(250000)
    ) PANEL_DEPOSIT (
        .clk(clk_25m),
        .reset(1'b0),
        .active_raw(~deposit_button_n),
        .command_level(deposit_button_level)
    );

    panel_level_filter #(
        .DEBOUNCE_CYCLES(250000)    // 10 ms at 25 MHz
    ) PANEL_HALT (
        .clk(clk_25m),
        .reset(1'b0),
        .active_raw(~halt_switch_n),
        .level(halt_switch)
    );

    panel_level_filter #(
        .DEBOUNCE_CYCLES(250000)
    ) PANEL_BREAK_ENABLE (
        .clk(clk_25m),
        .reset(1'b0),
        .active_raw(~break_enable_switch_n),
        .level(break_enable_switch)
    );

    panel_level_filter #(
        .DEBOUNCE_CYCLES(250000)
    ) PANEL_SSTEP (
        .clk(clk_25m),
        .reset(1'b0),
        .active_raw(~sstep_button_n),
        .level(sstep_button_level)
    );

    panel_level_filter #(
        .DEBOUNCE_CYCLES(250000)
    ) PANEL_MA_MD (
        .clk(clk_25m),
        .reset(1'b0),
        .active_raw(~ma_md_switch_n),
        .level(ma_md_switch_level)
    );

    // ------------------------------------------------------------
    // Automatic startup sequence
    // ------------------------------------------------------------
    // reset_pulse is held active for a while after configuration.
    // There is intentionally NO automatic CONT anymore: after FPGA
    // configuration OTTO remains halted until the physical CONT button
    // is pressed.

    reg [15:0] startup_counter = 0;
    reg        reset_pulse     = 1'b1;
    reg        startup_done    = 1'b0;

    always @(posedge core_clk) begin
        if (startup_counter < 16'd4000) begin
            reset_pulse     <= 1'b1;
            startup_counter <= startup_counter + 1'b1;
        end else if (startup_counter < 16'd4800) begin
            reset_pulse     <= 1'b0;
            startup_counter <= startup_counter + 1'b1;
        end else if (!startup_done) begin
            reset_pulse  <= 1'b0;
            startup_done <= 1'b1;
        end else begin
            reset_pulse <= 1'b0;
        end
    end

    // ------------------------------------------------------------
    // Old OTTO top-level signals
    // ------------------------------------------------------------

    wire [7:0] old_led;
    wire [6:0] seg_unused;
    wire       dp_unused;
    wire [3:0] an_unused;
    wire       old_rs_tx;
    wire       otto_rs_rx;
    wire       reader_run;
    wire [11:0] pc_display;
    wire [11:0] ac_display;
    wire [11:0] panel_addr_display;
    wire [11:0] panel_md_display;

    // Old front-panel buttons:
    // btn[0] = reset
    // btn[2] = CONT/RUN
    //
    // Dedicated SSTEP switch:
    //      SSTEP is a maintained front-panel switch, like on a real PDP-8.
    //      It does not start execution by itself.
    //      With SSTEP ON, pressing CONT executes one PDP-8 instruction and
    //      then stops again at the next instruction boundary.
    wire [3:0] old_btn;
    assign old_btn[0] = reset_pulse | clear_command_level;
    assign old_btn[1] = 1'b0;
    // CONT is the only command that starts execution.
    // SSTEP is a maintained switch: when ON, it only requests a stop at the
    // next PDP-8 instruction boundary.  Therefore moving SSTEP alone must not
    // advance the machine.
    assign old_btn[2] = cont_button_level;
    assign old_btn[3] = 1'b0;

    // Old switches:
    // sw[0] is BREAK ENABLE in the Colorlight control logic.
    // sw[7] is HALT switch in the old control logic.
    wire [7:0] old_sw;
    assign old_sw[0]   = break_enable_switch;
    assign old_sw[6:1] = 6'b000000;
    assign old_sw[7]   = halt_switch | sstep_button_level;

    PDP_top OTTO0 (
        .clk(core_clk),
        .sw(old_sw),
        .Led(old_led),
        .btn(old_btn),
        .seg(seg_unused),
        .dp(dp_unused),
        .an(an_unused),
        .RsTx(old_rs_tx),
        .RsRx(otto_rs_rx),
        .pc_display(pc_display),
        .ac_display(ac_display),
        .switch_register(effective_switch_register),
        .break_addr(break_addr_reg),
        .addrld_button_level(addrld_command_level),
        .brkset_button_level(brkset_button_level),
        .exam_button_level(exam_button_level),
        .deposit_button_level(deposit_button_level),
        .extaddr_button_level(extaddr_button_level),
        .panel_addr_display(panel_addr_display),
        .panel_md_display(panel_md_display)
    );

    // Serial input selection. Both inputs are FPGA-side TTL UART signals
    // after their respective adapters. Idle is high; an active bit is low.
    assign otto_rs_rx = uart_rx & rs232_rxd_in;

    // Serial output is duplicated to both physical interfaces.
    assign uart_tx       = old_rs_tx;
    assign rs232_txd_out = old_rs_tx;

    // Export paper-tape reader enable / reader run relay.
    // PDP_cpu.v maps it to old_led[0].
    assign reader_run = old_led[0];
    assign reader_run_out = reader_run;

    // RS-232 CTS handshake. reader_run is active high inside OTTO;
    // the MAX3232 inverts it, so drive the FPGA-side input low when
    // the external CTS line must be asserted.
    assign rs232_cts_out_n = ~reader_run;

    // 74HC595 8-digit display on P6.
    // Shows two PDP-8 words in octal: PC in the first four digits,
    // AC in the last four digits.
    // Decimal-point status indicators, logical display positions 0..7:
    //   0 1 2 3  = PC digits, left to right
    //   4 5 6 7  = AC digits, left to right
    // DP[0] = BREAK ENABLE, DP[3] = RUN, DP[4] = HALT, DP[7] = BREAK HIT.
    wire [7:0] display_dp_mask;
    assign display_dp_mask = {old_led[1], 2'b00, halt_switch, old_led[7], 2'b00, break_enable_switch};

    // ------------------------------------------------------------
    // Temporary full-front-panel diagnostic.
    // ------------------------------------------------------------
    // Normal mode, MA-MD switch OFF:
    //      left  = PC
    //      right = AC
    //
    // MA-MD mode, switch ON:
    //      left  = PANEL_ADDR
    //      right = PANEL_MD
    //
    // ADDRLD is now active:
    //      PC         <= SWREG
    //      PANEL_ADDR <= SWREG
    //      PANEL_MD   <= 0000
    //
    // EXAM, DEPOSIT and MA- are active.
    // EXTADDR is currently used as MA- / ADDR-.
    // BRKSET is active: it loads the top-level breakpoint register from SWREG.
    wire [11:0] switch_register;

    // DEC-style switch numbering:
    //      SW00 is the most significant bit.
    //      SW11 is the least significant bit.
    //
    // Physical switch logic:
    //      open / pulled-up       = 1
    //      closed to GND          = 0
    //
    // Therefore there is no electrical inversion here; only a bit-order remap.
    assign switch_register = {
        sw_n[0],   // SW00 -> bit 11
        sw_n[1],   // SW01 -> bit 10
        sw_n[2],   // SW02 -> bit 9
        sw_n[3],   // SW03 -> bit 8
        sw_n[4],   // SW04 -> bit 7
        sw_n[5],   // SW05 -> bit 6
        sw_n[6],   // SW06 -> bit 5
        sw_n[7],   // SW07 -> bit 4
        sw_n[8],   // SW08 -> bit 3
        sw_n[9],   // SW09 -> bit 2
        sw_n[10],  // SW10 -> bit 1
        sw_n[11]   // SW11 -> bit 0
    };

    // A7777 switch-register override guard.
    //
    // The ADDR LOAD command is generated in the 25 MHz panel domain and then
    // resynchronized inside the historical PDP core.  The PC load path is
    // therefore still active for a few core-clock cycles after a7777_addrld_level
    // has disappeared in this top-level domain.  If the forced 7777 value were
    // removed at the same instant, those trailing ADDR LOAD cycles could reload
    // the PC from the physical switch register.
    //
    // Keep the forced 7777 value alive for an additional guard interval after
    // the A7777 ADDR LOAD phase.  This makes the composite command behave as:
    //      CLEAR, then ADDR LOAD 7777, and nothing else.
    always @(posedge clk_25m) begin
        if (a7777_addrld_level) begin
            a7777_force_7777_level <= 1'b1;
            a7777_force_cnt        <= 32'd500000;   // 20 ms guard at 25 MHz
        end else if (a7777_force_cnt != 32'd0) begin
            a7777_force_7777_level <= 1'b1;
            a7777_force_cnt        <= a7777_force_cnt - 32'd1;
        end else begin
            a7777_force_7777_level <= 1'b0;
        end
    end

    // Effective front-panel commands.
    // A7777 overrides the switch-register value during ADDR LOAD and through
    // the guard interval above; ordinary switch-register operations and
    // SET BREAKPOINT continue to use the physical switches.
    assign clear_command_level  = clear_button_level  | a7777_clear_level;
    assign addrld_command_level = addrld_button_level | a7777_addrld_level;
    assign effective_switch_register =
        a7777_force_7777_level ? 12'o7777 : switch_register;

    // Front-panel breakpoint register.
    // Load it here, in the top-level panel logic, from the already-verified
    // physical switch register.  This avoids relying on a one-clock pulse inside
    // the old CPU control logic.
    //
    // The load is intentionally level-sensitive while SET BRK is held.
    reg [11:0] break_addr_reg = 12'o2671;

    always @(posedge core_clk) begin
        if (reset_pulse)
            break_addr_reg <= 12'o2671;      // same useful default as before
        else if (brkset_button_level)
            break_addr_reg <= switch_register;
    end

    // Front-panel address/data display registers are now generated inside
    // PDP_cpu.v / control_unit, in the same clock domain as the historical
    // CPU and close to the RAM access logic.  The top-level only displays
    // the exported panel_addr_display and panel_md_display values.

    wire ma_md_switch;
    assign ma_md_switch = ma_md_switch_level;

    wire [11:0] display_left_word;
    wire [11:0] display_right_word;

    // In MA-MD mode the normal display is PANEL_ADDR + PANEL_MD.
    // While BRKSET is held, temporarily show BREAK_ADDR + SWREG instead.
    // BRKSET uses the synchronized raw level, not the debounced level, so this
    // indication should appear immediately when the button is pressed.
    // This gives immediate visual confirmation that the breakpoint register
    // has been loaded with the intended front-panel address.
    assign display_left_word  = ma_md_switch ? (brkset_button_level ? break_addr_reg  : panel_addr_display) : pc_display;
    assign display_right_word = ma_md_switch ? (brkset_button_level ? switch_register : panel_md_display)   : ac_display;

    pdp_7seg_pc_ac_display DISPLAY0 (
        .clk(clk_25m),
        .pc(display_left_word),
        .ac(display_right_word),
        .dp_mask(display_dp_mask),
        .dio(disp_dio),
        .sck(disp_sck),
        .rck(disp_rck)
    );

    // Colorlight LED is active-low.
    // Show RUN/HALT state: ON while the old OTTO core is running.
    assign led = startup_done ? (old_led[7] ? 1'b0 : 1'b1) : 1'b1;

endmodule



// =============================================================================
// 8-digit 7-segment display driver for two PDP-8 octal words.
//
// Giovanni's module characteristics, measured on the real board:
//   - 2 x 74HC595 in cascade.
//   - 16 bits per digit refresh: segment byte then digit byte.
//   - Bits are shifted LSB first.
//   - Segment bits are active low.
//   - Digit select is one-hot active high.
//   - Physical segment order is G F E D C B A DP relative to standard A..G..DP.
//   - The two 4-digit halves must be exchanged.
//   - The display was mounted with PCB legends upright; the driver maps the
//     physical order so the human-readable display is left-to-right.
//
// Display format:
//      PPPPAAAA
//   where PPPP = PC in octal, AAAA = AC in octal.
// =============================================================================
module pdp_7seg_pc_ac_display (
    input  wire        clk,
    input  wire [11:0] pc,
    input  wire [11:0] ac,
    input  wire [7:0]  dp_mask,
    output wire        dio,
    output wire        sck,
    output wire        rck
);
    // Latch PC/AC once per full display frame, so all eight digits belong
    // to the same sampled instant.
    reg [11:0] pc_latched = 12'o0000;
    reg [11:0] ac_latched = 12'o0000;
    reg [7:0]  dp_mask_latched = 8'b00000000;

    // 1 ms per digit => about 125 complete 8-digit frames per second.
    localparam [15:0] REFRESH_DIV = 16'd25000; // 1 ms at 25 MHz

    reg [15:0] refresh_cnt = 16'd0;
    reg [2:0]  mux_digit   = 3'd0;
    reg        send_start  = 1'b0;

    always @(posedge clk) begin
        send_start <= 1'b0;

        if (refresh_cnt == REFRESH_DIV-1) begin
            refresh_cnt <= 16'd0;
            mux_digit   <= mux_digit + 3'd1;
            send_start  <= 1'b1;

            if (mux_digit == 3'd7) begin
                pc_latched      <= pc;
                ac_latched      <= ac;
                dp_mask_latched <= dp_mask;
            end
        end else begin
            refresh_cnt <= refresh_cnt + 16'd1;
        end
    end

    // Physical digit scan stays in the order that works electrically.
    // The content index is reversed so that the display reads left-to-right
    // in Giovanni's chosen physical orientation.
    wire [2:0] content_digit = 3'd7 - mux_digit;
    wire [3:0] octal_digit   = select_pc_ac_octal_digit(pc_latched, ac_latched, content_digit);

    wire       digit_dp      = dp_mask_latched[content_digit];
    wire [7:0] seg_standard  = seg7_octal_active_low(octal_digit, digit_dp);
    wire [7:0] seg_byte      = remap_standard_segments_to_module(seg_standard);

    // Digit byte: one-hot active-high, with the two 4-digit halves exchanged.
    wire [2:0] mapped_mux_digit = mux_digit ^ 3'd4;
    wire [7:0] digit_byte       = (8'b00000001 << mapped_mux_digit);

    // SO/Python order verified on the real module: first 8 shifted bits are
    // segments, next 8 are digit select. Since the sender is LSB-first,
    // seg_byte occupies bits [7:0].
    wire [15:0] so_order_word = {digit_byte, seg_byte};

    hc595_sender_lsb_first SEND595 (
        .clk   (clk),
        .start (send_start),
        .word  (so_order_word),
        .dio   (dio),
        .sck   (sck),
        .rck   (rck)
    );

    // Logical display positions 0..7, left to right:
    //   0 1 2 3 = PC[11:0] in octal
    //   4 5 6 7 = AC[11:0] in octal
    function [3:0] select_pc_ac_octal_digit;
        input [11:0] pc_word;
        input [11:0] ac_word;
        input [2:0]  pos;
        begin
            case (pos)
                3'd0: select_pc_ac_octal_digit = {1'b0, pc_word[11:9]};
                3'd1: select_pc_ac_octal_digit = {1'b0, pc_word[8:6]};
                3'd2: select_pc_ac_octal_digit = {1'b0, pc_word[5:3]};
                3'd3: select_pc_ac_octal_digit = {1'b0, pc_word[2:0]};
                3'd4: select_pc_ac_octal_digit = {1'b0, ac_word[11:9]};
                3'd5: select_pc_ac_octal_digit = {1'b0, ac_word[8:6]};
                3'd6: select_pc_ac_octal_digit = {1'b0, ac_word[5:3]};
                3'd7: select_pc_ac_octal_digit = {1'b0, ac_word[2:0]};
            endcase
        end
    endfunction

    // Remap a normal 7-segment byte to Giovanni's actual module wiring.
    // Standard byte convention used by seg7_octal_active_low:
    //   bit 0 = DP, bit 1 = A, bit 2 = B, bit 3 = C,
    //   bit 4 = D,  bit 5 = E, bit 6 = F, bit 7 = G.
    // Measured module response:
    //   sent bit 1 lights physical G
    //   sent bit 2 lights physical F
    //   sent bit 3 lights physical E
    //   sent bit 4 lights physical D
    //   sent bit 5 lights physical C
    //   sent bit 6 lights physical B
    //   sent bit 7 lights physical A
    //   sent bit 0 lights DP
    function [7:0] remap_standard_segments_to_module;
        input [7:0] x;
        begin
            remap_standard_segments_to_module[0] = x[0]; // DP -> DP
            remap_standard_segments_to_module[1] = x[7]; // physical G wants standard G
            remap_standard_segments_to_module[2] = x[6]; // physical F wants standard F
            remap_standard_segments_to_module[3] = x[5]; // physical E wants standard E
            remap_standard_segments_to_module[4] = x[4]; // physical D wants standard D
            remap_standard_segments_to_module[5] = x[3]; // physical C wants standard C
            remap_standard_segments_to_module[6] = x[2]; // physical B wants standard B
            remap_standard_segments_to_module[7] = x[1]; // physical A wants standard A
        end
    endfunction

    // Active-low 7-segment octal decoder.
    // Bit convention: bit 0 = DP, bit 1 = A, ..., bit 7 = G.
    function [7:0] seg7_octal_active_low;
        input [3:0] digit;
        input       dp_on;
        reg [7:0] s;
        begin
            case (digit[2:0])
                3'd0: s = 8'h81; // 0: A B C D E F
                3'd1: s = 8'hF3; // 1: B C
                3'd2: s = 8'h49; // 2: A B D E G
                3'd3: s = 8'h61; // 3: A B C D G
                3'd4: s = 8'h33; // 4: B C F G
                3'd5: s = 8'h25; // 5: A C D F G
                3'd6: s = 8'h05; // 6: A C D E F G
                3'd7: s = 8'hF1; // 7: A B C
            endcase

            if (dp_on)
                s[0] = 1'b0;
            else
                s[0] = 1'b1;

            seg7_octal_active_low = s;
        end
    endfunction
endmodule


module hc595_sender_lsb_first (
    input  wire        clk,
    input  wire        start,
    input  wire [15:0] word,
    output reg         dio = 1'b1,
    output reg         sck = 1'b0,
    output reg         rck = 1'b0
);
    // Conservative shift rate for 74HC595.
    localparam [7:0] BIT_DIV = 8'd40;

    reg [15:0] shreg     = 16'hFFFF;
    reg [4:0]  bits_left = 5'd0;
    reg [7:0]  div_cnt   = 8'd0;

    localparam [2:0]
        ST_IDLE       = 3'd0,
        ST_SETUP      = 3'd1,
        ST_SCK_HIGH   = 3'd2,
        ST_SCK_LOW    = 3'd3,
        ST_LATCH_HIGH = 3'd4,
        ST_LATCH_LOW  = 3'd5;

    reg [2:0] state = ST_IDLE;

    always @(posedge clk) begin
        case (state)
            ST_IDLE: begin
                sck <= 1'b0;
                rck <= 1'b0;
                if (start) begin
                    shreg     <= word;
                    bits_left <= 5'd16;
                    div_cnt   <= BIT_DIV;
                    state     <= ST_SETUP;
                end
            end

            ST_SETUP: begin
                sck <= 1'b0;
                rck <= 1'b0;
                dio <= shreg[0];        // bit 0 first
                if (div_cnt != 8'd0) begin
                    div_cnt <= div_cnt - 8'd1;
                end else begin
                    div_cnt <= BIT_DIV;
                    state   <= ST_SCK_HIGH;
                end
            end

            ST_SCK_HIGH: begin
                sck <= 1'b1;            // 74HC595 shifts on rising edge
                if (div_cnt != 8'd0) begin
                    div_cnt <= div_cnt - 8'd1;
                end else begin
                    div_cnt <= BIT_DIV;
                    state   <= ST_SCK_LOW;
                end
            end

            ST_SCK_LOW: begin
                sck   <= 1'b0;
                shreg <= {1'b1, shreg[15:1]};
                if (bits_left != 5'd1) begin
                    bits_left <= bits_left - 5'd1;
                    div_cnt   <= BIT_DIV;
                    state     <= ST_SETUP;
                end else begin
                    bits_left <= 5'd0;
                    div_cnt   <= BIT_DIV;
                    state     <= ST_LATCH_HIGH;
                end
            end

            ST_LATCH_HIGH: begin
                rck <= 1'b1;
                if (div_cnt != 8'd0) begin
                    div_cnt <= div_cnt - 8'd1;
                end else begin
                    div_cnt <= BIT_DIV;
                    state   <= ST_LATCH_LOW;
                end
            end

            ST_LATCH_LOW: begin
                rck <= 1'b0;
                state <= ST_IDLE;
            end

            default: begin
                state <= ST_IDLE;
            end
        endcase
    end
endmodule

// =============================================================================
// Front-panel input helpers.
//
// These modules run from the fixed Colorlight 25 MHz oscillator, not from the
// PDP core clock.  This keeps the panel timing valid if the CPU clock divider is
// changed later.
// =============================================================================
module panel_a7777_command #(
    parameter integer CLEAR_CYCLES   = 500000,   // 20 ms at 25 MHz
    parameter integer GAP_CYCLES     = 125000,   // 5 ms at 25 MHz
    parameter integer ADDRLD_CYCLES  = 500000,   // 20 ms at 25 MHz
    parameter integer RELEASE_CYCLES = 250000    // 10 ms at 25 MHz
)(
    input  wire clk,
    input  wire reset,
    input  wire active_raw,
    output reg  clear_level  = 1'b0,
    output reg  addrld_level = 1'b0
);
    reg [2:0] sync = 3'b000;
    reg [2:0] state = 3'd0;
    reg [31:0] cnt = 32'd0;
    reg [31:0] release_cnt = 32'd0;

    wire active = sync[2];

    localparam [2:0]
        ST_ARMED        = 3'd0,
        ST_CLEAR        = 3'd1,
        ST_GAP          = 3'd2,
        ST_ADDRLD       = 3'd3,
        ST_WAIT_RELEASE = 3'd4;

    always @(posedge clk) begin
        sync <= {sync[1:0], active_raw};

        if (reset) begin
            state        <= ST_ARMED;
            cnt          <= 32'd0;
            release_cnt  <= 32'd0;
            clear_level  <= 1'b0;
            addrld_level <= 1'b0;
        end else begin
            clear_level  <= 1'b0;
            addrld_level <= 1'b0;

            case (state)
                ST_ARMED: begin
                    release_cnt <= 32'd0;
                    if (active) begin
                        cnt   <= CLEAR_CYCLES;
                        state <= ST_CLEAR;
                    end
                end

                ST_CLEAR: begin
                    clear_level <= 1'b1;
                    if (cnt != 32'd0) begin
                        cnt <= cnt - 32'd1;
                    end else begin
                        cnt   <= GAP_CYCLES;
                        state <= ST_GAP;
                    end
                end

                ST_GAP: begin
                    if (cnt != 32'd0) begin
                        cnt <= cnt - 32'd1;
                    end else begin
                        cnt   <= ADDRLD_CYCLES;
                        state <= ST_ADDRLD;
                    end
                end

                ST_ADDRLD: begin
                    addrld_level <= 1'b1;
                    if (cnt != 32'd0) begin
                        cnt <= cnt - 32'd1;
                    end else begin
                        state <= ST_WAIT_RELEASE;
                        release_cnt <= 32'd0;
                    end
                end

                ST_WAIT_RELEASE: begin
                    if (active) begin
                        release_cnt <= 32'd0;
                    end else begin
                        if (release_cnt >= RELEASE_CYCLES) begin
                            state       <= ST_ARMED;
                            release_cnt <= 32'd0;
                        end else begin
                            release_cnt <= release_cnt + 32'd1;
                        end
                    end
                end

                default: begin
                    state <= ST_ARMED;
                end
            endcase
        end
    end
endmodule


module panel_momentary_command #(
    parameter integer PULSE_CYCLES   = 500000,   // 20 ms at 25 MHz
    parameter integer RELEASE_CYCLES = 250000    // 10 ms at 25 MHz
)(
    input  wire clk,
    input  wire reset,
    input  wire active_raw,
    output reg  command_level = 1'b0
);
    reg [2:0] sync = 3'b000;
    reg       armed = 1'b1;
    reg [31:0] pulse_cnt = 32'd0;
    reg [31:0] release_cnt = 32'd0;

    wire active = sync[2];

    always @(posedge clk) begin
        sync <= {sync[1:0], active_raw};

        if (reset) begin
            armed         <= 1'b1;
            pulse_cnt     <= 32'd0;
            release_cnt   <= 32'd0;
            command_level <= 1'b0;
        end else begin
            if (pulse_cnt != 32'd0) begin
                pulse_cnt     <= pulse_cnt - 32'd1;
                command_level <= 1'b1;
            end else begin
                command_level <= 1'b0;
            end

            if (armed) begin
                release_cnt <= 32'd0;
                if (active) begin
                    armed         <= 1'b0;
                    pulse_cnt     <= PULSE_CYCLES;
                    command_level <= 1'b1;
                end
            end else begin
                if (active) begin
                    release_cnt <= 32'd0;
                end else begin
                    if (release_cnt >= RELEASE_CYCLES) begin
                        armed       <= 1'b1;
                        release_cnt <= 32'd0;
                    end else begin
                        release_cnt <= release_cnt + 32'd1;
                    end
                end
            end
        end
    end
endmodule


module panel_level_filter #(
    parameter integer DEBOUNCE_CYCLES = 250000   // 10 ms at 25 MHz
)(
    input  wire clk,
    input  wire reset,
    input  wire active_raw,
    output reg  level = 1'b0
);
    reg [2:0] sync = 3'b000;
    reg [31:0] cnt = 32'd0;

    wire active = sync[2];

    always @(posedge clk) begin
        sync <= {sync[1:0], active_raw};

        if (reset) begin
            level <= 1'b0;
            cnt   <= 32'd0;
        end else begin
            if (active == level) begin
                cnt <= 32'd0;
            end else begin
                if (cnt >= DEBOUNCE_CYCLES) begin
                    level <= active;
                    cnt   <= 32'd0;
                end else begin
                    cnt <= cnt + 32'd1;
                end
            end
        end
    end
endmodule

