Skip to content

nd120_diag_print

Source: Verilog/fpga/mister/rtl/nd120_diag_print.v

Hierarchy: not instantiated by any of the 9 build tops (elaborated by yosys).

Module hierarchy - All modules

nd120_diag_print symbol

Schematic

Schematic not generated: netlistsvg failed: RangeError: Maximum call stack size exceeded.

Description

nd120_diag_print.v - CPU state, printed onto the console WHY THIS EXISTS (31-AUG-2026)


On MiSTer the console, the operator panel and the uptime counter all work - they live in the pixel clock domain - while the CPU shows no sign of life: no OPCOM '#', no echo, MIPS 00.00. Everything the build reports is clean (0 errors, WCS microcode initialised from MIF, CPU clock a real PLL output on a global network, timing met), so the remaining question is not "does a report look wrong" but "what is the CPU actually DOING". A screenshot of the running core can be pulled off the board over ssh ("screenshot" > /dev/MiSTer_cmd), so anything printed on the console can be read back without anyone watching the monitor. WHAT EACH FIELD ANSWERS


CK free-running counter clocked by clk_cpu. Proves the CPU clock is toggling in silicon and that the CPU reset was released - neither of which simulation or the timing report can tell us. CSA the microcode address. NOTE its limits: sampled once a second, it ALIASES against a tight loop and shows arbitrary points inside it, not the loop. Use nd120_csa_trace.v for the real sequence. PIL processor interrupt level. AL active-level mask. R cpu_rst_n. N RUN_n (active low). ST the self-test failure branch: hit flag / entry count, and R2 - the error number STERR exists to display (see rtl/nd120_sterr_catch.v). LB is the register slot "R2" decoded to. IQ the raw interrupt-request vector, ALREADY INVERTED by the caller so a 1 bit means "this level is requesting". The core drives it active low as DEBUG_IREQ_15_0_N; printing it raw would mean reading a wall of ones and mentally inverting every bit. Values are OCTAL. This is an octal machine and these are compared against octal microcode listings; hex would mean converting by hand every time. CLOCK DOMAINS. Everything from the CPU belongs to clk_cpu; this module runs on clk_sys and samples through two flops. That is enough for a DISPLAY - a sample taken mid-change can show a torn value for one line - but it is not a synchroniser for control logic, and this module drives nothing but text. DIAGNOSTIC SCAFFOLDING, compiled in only when ND120_DIAG_PRINT is defined. It is not part of the machine.

Parameters

Parameter Default
CLK_HZ 40_000_000 //! clk_sys rate

Ports

Direction Width Name Description
input 1 clk clk_sys (pixel/console domain)
input 1 rst_n (active low)
input [15:0] cpu_ticks free-running clk_cpu counter
input [12:0] csa microcode address
input [3:0] pil processor interrupt level
input [15:0] actlv active-level mask, one bit per level
input 1 sterr_hit STERR was reached at least once
input [7:0] sterr_count STERR entries (1 may be the WCS loader)
input [15:0] sterr_r2 R2 latched at STERR - the error number
input [3:0] sterr_lba which register slot "R2" decoded to
input [15:0] ireq interrupt requests, ACTIVE HIGH
input 1 pie_hit PIE was read at least once
input [7:0] pie_count PIE reads at microcode 001011
input [15:0] pie the PIE value the 001013 branch uses
input 1 cpu_rst_n (active low) CPU reset, active low
input 1 run_n (active low) CPU RUN_n, active low
output 1 byte_valid
output [7:0] byte_data
input 1 byte_ready

Verilog source

Verilog/fpga/mister/rtl/nd120_diag_print.v on GitHub.

Show the Verilog of nd120_diag_print (338 lines)
/*****************************************************************************
 *  nd120_diag_print.v - CPU state, printed onto the console                  *
 *                                                                            *
 *  WHY THIS EXISTS (31-AUG-2026)                                             *
 *  ------------------------------                                            *
 *  On MiSTer the console, the operator panel and the uptime counter all work *
 *  - they live in the pixel clock domain - while the CPU shows no sign of    *
 *  life: no OPCOM '#', no echo, MIPS 00.00. Everything the build reports is  *
 *  clean (0 errors, WCS microcode initialised from MIF, CPU clock a real PLL *
 *  output on a global network, timing met), so the remaining question is not *
 *  "does a report look wrong" but "what is the CPU actually DOING".          *
 *                                                                            *
 *  A screenshot of the running core can be pulled off the board over ssh     *
 *  ("screenshot" > /dev/MiSTer_cmd), so anything printed on the console can  *
 *  be read back without anyone watching the monitor.                         *
 *                                                                            *
 *  WHAT EACH FIELD ANSWERS                                                   *
 *  -----------------------                                                   *
 *    CK   free-running counter clocked by clk_cpu. Proves the CPU clock is   *
 *         toggling in silicon and that the CPU reset was released - neither  *
 *         of which simulation or the timing report can tell us.              *
 *    CSA  the microcode address. NOTE its limits: sampled once a second, it  *
 *         ALIASES against a tight loop and shows arbitrary points inside it, *
 *         not the loop. Use nd120_csa_trace.v for the real sequence.         *
 *    PIL  processor interrupt level.    AL  active-level mask.               *
 *    R    cpu_rst_n.                    N   RUN_n (active low).              *
 *    ST   the self-test failure branch: hit flag / entry count, and R2 -     *
 *         the error number STERR exists to display (see                      *
 *         rtl/nd120_sterr_catch.v). LB is the register slot "R2" decoded to. *
 *    IQ   the raw interrupt-request vector, ALREADY INVERTED by the caller   *
 *         so a 1 bit means "this level is requesting". The core drives it    *
 *         active low as DEBUG_IREQ_15_0_N; printing it raw would mean        *
 *         reading a wall of ones and mentally inverting every bit.           *
 *                                                                            *
 *  Values are OCTAL. This is an octal machine and these are compared against *
 *  octal microcode listings; hex would mean converting by hand every time.   *
 *                                                                            *
 *  CLOCK DOMAINS. Everything from the CPU belongs to clk_cpu; this module    *
 *  runs on clk_sys and samples through two flops. That is enough for a       *
 *  DISPLAY - a sample taken mid-change can show a torn value for one line -  *
 *  but it is not a synchroniser for control logic, and this module drives    *
 *  nothing but text.                                                         *
 *                                                                            *
 *  DIAGNOSTIC SCAFFOLDING, compiled in only when ND120_DIAG_PRINT is         *
 *  defined. It is not part of the machine.                                   *
 *****************************************************************************/

`default_nettype none

module nd120_diag_print #(
    parameter integer CLK_HZ = 40_000_000  //! clk_sys rate, sets the one-second tick
) (
    input  wire        clk,      //! clk_sys (pixel/console domain)
    input  wire        rst_n,

    // Signals from the CPU domain - sampled, see the CLOCK DOMAINS note above.
    input  wire [15:0] cpu_ticks,    //! free-running clk_cpu counter
    input  wire [12:0] csa,          //! microcode address
    input  wire [ 3:0] pil,          //! processor interrupt level
    input  wire [15:0] actlv,        //! active-level mask, one bit per level
    input  wire        sterr_hit,    //! STERR was reached at least once
    input  wire [ 7:0] sterr_count,  //! STERR entries (1 may be the WCS loader)
    input  wire [15:0] sterr_r2,     //! R2 latched at STERR - the error number
    input  wire [ 3:0] sterr_lba,    //! which register slot "R2" decoded to
    input  wire [15:0] ireq,         //! interrupt requests, ACTIVE HIGH
    input  wire        pie_hit,      //! PIE was read at least once
    input  wire [ 7:0] pie_count,    //! PIE reads at microcode 001011
    input  wire [15:0] pie,          //! the PIE value the 001013 branch uses
    input  wire        cpu_rst_n,    //! CPU reset, active low
    input  wire        run_n,        //! CPU RUN_n, active low

    // Byte stream into the console, same handshake as the CPU's own output.
    output reg         byte_valid,
    output reg  [7:0]  byte_data,
    input  wire        byte_ready
);

  //--------------------------------------------------------------------------
  // Sample the CPU-domain signals
  //--------------------------------------------------------------------------
  reg [15:0] s_ticks_m, s_ticks;
  reg [12:0] s_csa_m,   s_csa;
  reg [ 3:0] s_pil_m,   s_pil;
  reg [15:0] s_actlv_m, s_actlv;
  reg        s_shit_m,  s_shit;
  reg [ 7:0] s_scnt_m,  s_scnt;
  reg [15:0] s_sr2_m,   s_sr2;
  reg [ 3:0] s_slba_m,  s_slba;
  reg [15:0] s_ireq_m,  s_ireq;
  reg        s_phit_m,  s_phit;
  reg [ 7:0] s_pcnt_m,  s_pcnt;
  reg [15:0] s_pie_m,   s_pie;
  reg        s_rst_m,   s_rst;
  reg        s_run_m,   s_run;

  always @(posedge clk) begin
    s_ticks_m <= cpu_ticks;   s_ticks <= s_ticks_m;
    s_csa_m   <= csa;         s_csa   <= s_csa_m;
    s_pil_m   <= pil;         s_pil   <= s_pil_m;
    s_actlv_m <= actlv;       s_actlv <= s_actlv_m;
    s_shit_m  <= sterr_hit;   s_shit  <= s_shit_m;
    s_scnt_m  <= sterr_count; s_scnt  <= s_scnt_m;
    s_sr2_m   <= sterr_r2;    s_sr2   <= s_sr2_m;
    s_slba_m  <= sterr_lba;   s_slba  <= s_slba_m;
    s_ireq_m  <= ireq;        s_ireq  <= s_ireq_m;
    s_phit_m  <= pie_hit;     s_phit  <= s_phit_m;
    s_pcnt_m  <= pie_count;   s_pcnt  <= s_pcnt_m;
    s_pie_m   <= pie;         s_pie   <= s_pie_m;
    s_rst_m   <= cpu_rst_n;   s_rst   <= s_rst_m;
    s_run_m   <= run_n;       s_run   <= s_run_m;
  end

  //--------------------------------------------------------------------------
  // One-second tick
  //--------------------------------------------------------------------------
  localparam integer TICK_MAX = CLK_HZ - 1;

  reg [25:0] r_tick_cnt;
  reg        r_start;

  always @(posedge clk) begin
    if (!rst_n) begin
      r_tick_cnt <= 26'd0;
      r_start    <= 1'b0;
    end else if (r_tick_cnt >= TICK_MAX[25:0]) begin
      r_tick_cnt <= 26'd0;
      r_start    <= 1'b1;
    end else begin
      r_tick_cnt <= r_tick_cnt + 26'd1;
      r_start    <= 1'b0;
    end
  end

  //--------------------------------------------------------------------------
  // Two lines, emitted one character per accepted handshake:
  //
  //   CK nnnnnn CSA nnnnn PIL nn AL nnnnnn R n N n<CR><LF>
  //   ST n/nnn R2 nnnnnn LB nn IQ nnnnnn<CR><LF>
  //
  // Every field is latched at the START of the pair, so both lines describe
  // the same instant - fields sampled as they print would be smeared across
  // the ~80 character times the pair takes to send.
  //--------------------------------------------------------------------------
  localparam integer LEN = 98;

  reg [15:0] l_ticks;
  reg [12:0] l_csa;
  reg [ 3:0] l_pil;
  reg [15:0] l_actlv;
  reg        l_shit;
  reg [ 7:0] l_scnt;
  reg [15:0] l_sr2;
  reg [ 3:0] l_slba;
  reg [15:0] l_ireq;
  reg        l_phit;
  reg [ 7:0] l_pcnt;
  reg [15:0] l_pie;
  reg        l_rst;
  reg        l_run;

  reg [6:0]  r_idx;      //! 0..LEN-1 while sending, LEN = idle
  reg [7:0]  s_char;

  //! One octal digit, as ASCII.
  function [7:0] oct;
    input [2:0] v;
    begin
      oct = 8'h30 + {5'b0, v};
    end
  endfunction

  always @(*) begin
    case (r_idx)
      // ---- line 1 ----
      7'd0:  s_char = "C";
      7'd1:  s_char = "K";
      7'd2:  s_char = " ";
      // 16-bit counter, 6 octal digits (top digit is 1 bit wide)
      7'd3:  s_char = oct({2'b0, l_ticks[15]});
      7'd4:  s_char = oct(l_ticks[14:12]);
      7'd5:  s_char = oct(l_ticks[11:9]);
      7'd6:  s_char = oct(l_ticks[8:6]);
      7'd7:  s_char = oct(l_ticks[5:3]);
      7'd8:  s_char = oct(l_ticks[2:0]);
      7'd9:  s_char = " ";
      7'd10: s_char = "C";
      7'd11: s_char = "S";
      7'd12: s_char = "A";
      7'd13: s_char = " ";
      // 13-bit microcode address, 5 octal digits
      7'd14: s_char = oct({2'b0, l_csa[12]});
      7'd15: s_char = oct(l_csa[11:9]);
      7'd16: s_char = oct(l_csa[8:6]);
      7'd17: s_char = oct(l_csa[5:3]);
      7'd18: s_char = oct(l_csa[2:0]);
      7'd19: s_char = " ";
      7'd20: s_char = "P";
      7'd21: s_char = "I";
      7'd22: s_char = "L";
      7'd23: s_char = " ";
      7'd24: s_char = oct({2'b0, l_pil[3]});
      7'd25: s_char = oct(l_pil[2:0]);
      7'd26: s_char = " ";
      7'd27: s_char = "A";
      7'd28: s_char = "L";
      7'd29: s_char = " ";
      7'd30: s_char = oct({2'b0, l_actlv[15]});
      7'd31: s_char = oct(l_actlv[14:12]);
      7'd32: s_char = oct(l_actlv[11:9]);
      7'd33: s_char = oct(l_actlv[8:6]);
      7'd34: s_char = oct(l_actlv[5:3]);
      7'd35: s_char = oct(l_actlv[2:0]);
      7'd36: s_char = " ";
      7'd37: s_char = "R";
      7'd38: s_char = " ";
      7'd39: s_char = l_rst ? "1" : "0";
      7'd40: s_char = " ";
      7'd41: s_char = "N";
      7'd42: s_char = " ";
      7'd43: s_char = l_run ? "1" : "0";
      7'd44: s_char = 8'h0D;
      7'd45: s_char = 8'h0A;
      // ---- line 2 ----
      7'd46: s_char = "S";
      7'd47: s_char = "T";
      7'd48: s_char = " ";
      7'd49: s_char = l_shit ? "1" : "0";
      7'd50: s_char = "/";
      // 8-bit entry count, 3 octal digits (top digit is 2 bits wide)
      7'd51: s_char = oct({1'b0, l_scnt[7:6]});
      7'd52: s_char = oct(l_scnt[5:3]);
      7'd53: s_char = oct(l_scnt[2:0]);
      7'd54: s_char = " ";
      7'd55: s_char = "R";
      7'd56: s_char = "2";
      7'd57: s_char = " ";
      7'd58: s_char = oct({2'b0, l_sr2[15]});
      7'd59: s_char = oct(l_sr2[14:12]);
      7'd60: s_char = oct(l_sr2[11:9]);
      7'd61: s_char = oct(l_sr2[8:6]);
      7'd62: s_char = oct(l_sr2[5:3]);
      7'd63: s_char = oct(l_sr2[2:0]);
      7'd64: s_char = " ";
      7'd65: s_char = "L";
      7'd66: s_char = "B";
      7'd67: s_char = " ";
      7'd68: s_char = oct({2'b0, l_slba[3]});
      7'd69: s_char = oct(l_slba[2:0]);
      7'd70: s_char = " ";
      7'd71: s_char = "I";
      7'd72: s_char = "Q";
      7'd73: s_char = " ";
      7'd74: s_char = oct({2'b0, l_ireq[15]});
      7'd75: s_char = oct(l_ireq[14:12]);
      7'd76: s_char = oct(l_ireq[11:9]);
      7'd77: s_char = oct(l_ireq[8:6]);
      7'd78: s_char = oct(l_ireq[5:3]);
      7'd79: s_char = oct(l_ireq[2:0]);
      // PIE, latched at microcode 001011 - the value the 001013 branch is
      // decided on, and the first place this board diverges from a booting
      // machine. n/nnn is hit/count, the same shape as ST above.
      7'd80: s_char = " ";
      7'd81: s_char = "P";
      7'd82: s_char = "E";
      7'd83: s_char = " ";
      7'd84: s_char = l_phit ? "1" : "0";
      7'd85: s_char = "/";
      7'd86: s_char = oct({1'b0, l_pcnt[7:6]});
      7'd87: s_char = oct(l_pcnt[5:3]);
      7'd88: s_char = oct(l_pcnt[2:0]);
      7'd89: s_char = " ";
      7'd90: s_char = oct({2'b0, l_pie[15]});
      7'd91: s_char = oct(l_pie[14:12]);
      7'd92: s_char = oct(l_pie[11:9]);
      7'd93: s_char = oct(l_pie[8:6]);
      7'd94: s_char = oct(l_pie[5:3]);
      7'd95: s_char = oct(l_pie[2:0]);
      7'd96: s_char = 8'h0D;
      7'd97: s_char = 8'h0A;
      default: s_char = " ";
    endcase
  end

  always @(posedge clk) begin
    if (!rst_n) begin
      r_idx      <= LEN[6:0];
      byte_valid <= 1'b0;
      byte_data  <= 8'h00;
      l_ticks    <= 16'd0;
      l_csa      <= 13'd0;
      l_pil      <= 4'd0;
      l_actlv    <= 16'd0;
      l_shit     <= 1'b0;
      l_scnt     <= 8'd0;
      l_sr2      <= 16'd0;
      l_slba     <= 4'd0;
      l_ireq     <= 16'd0;
      l_phit     <= 1'b0;
      l_pcnt     <= 8'd0;
      l_pie      <= 16'd0;
      l_rst      <= 1'b0;
      l_run      <= 1'b0;
    end else begin
      if (r_idx == LEN[6:0]) begin
        // Idle. A tick starts the pair and freezes the values it will show.
        byte_valid <= 1'b0;
        if (r_start) begin
          l_ticks <= s_ticks;
          l_csa   <= s_csa;
          l_pil   <= s_pil;
          l_actlv <= s_actlv;
          l_shit  <= s_shit;
          l_scnt  <= s_scnt;
          l_sr2   <= s_sr2;
          l_slba  <= s_slba;
          l_ireq  <= s_ireq;
          l_phit  <= s_phit;
          l_pcnt  <= s_pcnt;
          l_pie   <= s_pie;
          l_rst   <= s_rst;
          l_run   <= s_run;
          r_idx   <= 7'd0;
        end
      end else begin
        // Sending. Hold the character until the console takes it.
        byte_valid <= 1'b1;
        byte_data  <= s_char;
        if (byte_valid && byte_ready) begin
          byte_valid <= 1'b0;
          r_idx      <= r_idx + 7'd1;
        end
      end
    end
  end

endmodule

`default_nettype wire