Skip to content

status_printer

Source: Verilog/fpga/tang-nano-20k/sd-fat-test/src/status_printer.v

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

Module hierarchy - All modules

status_printer symbol

Schematic

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

Description

Status message printer for the SD-FAT test Streams one of a set of fixed ASCII status lines through an external uart_tx (shared with hex_dumper, muxed by the top level). Same byte pump pattern as sdram-test/src/msg_printer.v. NOTE: \r is not a legal Verilog string escape - CR must be octal \015. Every message must stay within 64 characters: strchar's window is [8*64-1:0] and idx is 6 bits, so a longer string is silently truncated and then wraps. That is why the menu is TWO messages. Last reviewed: 10-AUG-2026 Ronny Hansen

Ports

Direction Width Name Description
input 1 clk
input 1 rst_n (active low)
input 1 start 1-cycle pulse; only when busy=0
input [5:0] msg MSG_* selector, latched at start
output 1 busy high until the whole message has left the UART
output [7:0] tx_data
output 1 tx_valid
input 1 tx_busy

Verilog source

Verilog/fpga/tang-nano-20k/sd-fat-test/src/status_printer.v on GitHub.

Show the Verilog of status_printer (304 lines)
/****************************************************************************
** Status message printer for the SD-FAT test                              **
**                                                                         **
** Streams one of a set of fixed ASCII status lines through an external    **
** uart_tx (shared with hex_dumper, muxed by the top level). Same byte     **
** pump pattern as sdram-test/src/msg_printer.v.                           **
**                                                                         **
** NOTE: \r is not a legal Verilog string escape - CR must be octal \015.  **
**                                                                         **
** Every message must stay within 64 characters: strchar's window is       **
** [8*64-1:0] and idx is 6 bits, so a longer string is silently truncated  **
** and then wraps. That is why the menu is TWO messages.                   **
**                                                                         **
** Last reviewed: 10-AUG-2026                                              **
** Ronny Hansen                                                            **
*****************************************************************************/

`include "sd_fat_test_config.vh"

module status_printer (
    input clk,
    input rst_n,

    input       start,  // 1-cycle pulse; only when busy=0
    input [5:0] msg,    // MSG_* selector, latched at start
    output      busy,   // high until the whole message has left the UART

    // handshake to the shared uart_tx (top level muxes this with hex_dumper)
    output reg [7:0] tx_data,
    output reg       tx_valid,
    input            tx_busy
);

  // Message selectors (top level uses the same values)
  localparam MSG_BANNER= 6'd0;
  localparam MSG_CARD_SDV1= 6'd1;
  localparam MSG_CARD_SDV2= 6'd2;
  localparam MSG_CARD_SDHC= 6'd3;
  localparam MSG_FS_FAT16= 6'd4;
  localparam MSG_FS_FAT32= 6'd5;
  localparam MSG_FS_UNKNOWN= 6'd6;
  localparam MSG_FILE_FOUND= 6'd7;
  localparam MSG_FILE_NOTFND= 6'd8;
  localparam MSG_ERR_CARD_TO= 6'd9;
  localparam MSG_ERR_SCAN_TO= 6'd10;
  localparam MSG_ERR_TRUNC= 6'd11;
  localparam MSG_MENU= 6'd12;
  localparam MSG_NOTIMPL= 6'd13;
  localparam MSG_SD_NOTCHK= 6'd14;  // menu SD status line (14 + status code)
  localparam MSG_SD_NOCARD= 6'd15;
  localparam MSG_SD_ERROR= 6'd16;
  localparam MSG_SD_OK= 6'd17;
  localparam MSG_COPYING= 6'd18;
  localparam MSG_COPY_DONE= 6'd19;
  localparam MSG_ERR_NOTGT= 6'd20;
  localparam MSG_ERR_SMALL= 6'd21;
  localparam MSG_ERR_WRITE= 6'd22;
  localparam MSG_BLK_WRITING= 6'd23;
  localparam MSG_BLK_DONE= 6'd24;
  localparam MSG_ERR_RANGE= 6'd25;
  localparam MSG_HELP1= 6'd26;
  localparam MSG_HELP2= 6'd27;
  localparam MSG_HELP3= 6'd28;
  localparam MSG_HELP4= 6'd29;
  localparam MSG_HELP5= 6'd30;
  localparam MSG_HELP6= 6'd31;
  localparam MSG_HELP7= 6'd32;
  localparam MSG_ERR_IOSZ= 6'd33;
  localparam MSG_IOW_RUN= 6'd34;
  localparam MSG_IOR_RUN= 6'd35;
  localparam MSG_SCANNING= 6'd36;
  localparam MSG_ERR_READ= 6'd37;
  localparam MSG_ERR_FATRD= 6'd38;
  localparam MSG_ERR_FATCOR= 6'd39;
  localparam MSG_MENU2= 6'd40;     // second menu line (the streaming commands)
  localparam MSG_ASK_BLK= 6'd41;
  localparam MSG_ASK_SEC= 6'd42;
  localparam MSG_ASK_NAME= 6'd43;
  localparam MSG_ERR_NUM= 6'd44;
  localparam MSG_AT_SEC= 6'd45;    // followed by 8 hex digits from the top level
  localparam MSG_NAME_SET= 6'd46;
  localparam MSG_HELP8= 6'd47;     // help continues here: 33..39 were taken
  localparam MSG_HELP9= 6'd48;     // before the streaming commands existed
  localparam MSG_HELP10= 6'd49;
  localparam MSG_HELP11= 6'd50;
  localparam MSG_HELP12= 6'd51;
  localparam MSG_HELP13= 6'd52;
  // multi-block range read (menu R): prompts, progress and the summary
  localparam MSG_ASK_STA= 6'd53;
  localparam MSG_ASK_CNT= 6'd54;
  localparam MSG_R_RUN= 6'd55;
  localparam MSG_R_AT= 6'd56;      // + 8 hex digits: block reached
  localparam MSG_R_BLOCKS= 6'd57;  // + 8 hex digits
  localparam MSG_R_SECS= 6'd58;    // + 8 hex digits
  localparam MSG_R_CHK= 6'd59;     // + 8 hex digits
  localparam MSG_R_CYC= 6'd60;     // + 8 hex digits
  localparam MSG_R_PASS= 6'd61;
  localparam MSG_R_FAIL= 6'd62;

  // Fixed strings (longest is 55 chars; strchar window is 64)
  localparam S_BANNER      = "\015\012SD-FAT TEST 10-JUL-2026\015\012";
  localparam S_CARD_SDV1   = "CARD: SDV1\015\012";
  localparam S_CARD_SDV2   = "CARD: SDV2\015\012";
  localparam S_CARD_SDHC   = "CARD: SDHC\015\012";
  localparam S_FS_FAT16    = "FS: FAT16\015\012";
  localparam S_FS_FAT32    = "FS: FAT32\015\012";
  localparam S_FS_UNKNOWN  = "FS: UNKNOWN\015\012";
  localparam S_FILE_FOUND  = "FILE: FOUND\015\012";
  localparam S_FILE_NOTFND = "FILE: NOT FOUND\015\012";
  localparam S_ERR_CARD_TO = "ERROR: CARD INIT TIMEOUT\015\012";
  localparam S_ERR_SCAN_TO = "ERROR: FS SCAN TIMEOUT\015\012";
  localparam S_ERR_TRUNC   = "ERROR: FILE TRUNCATED TO BUFFER\015\012";
  // The menu is printed as two messages (64-char limit, see the header).
  // The read-only build must not OFFER a command it does not contain, so
  // the writing keys are absent from the text as well as from the FSM.
`ifdef SDFAT_TEST_READONLY
  localparam S_MENU        = "\015\0121=LIST 2=DUMP 5=CHECK 8=BLOCK 9=SECTOR\015\012";
  localparam S_MENU2       = "R=RANGE N=NAME H=HELP\015\012# ";
`else
  localparam S_MENU        = "\015\0121=LIST 2=DUMP 3=COPY 4=WRBLK1 5=CHECK 6=WS 7=RS\015\012";
  localparam S_MENU2       = "8=BLOCK 9=SECTOR R=RANGE N=NAME H=HELP\015\012# ";
`endif
  localparam S_NOTIMPL     = "NOT IMPLEMENTED\015\012";
  localparam S_SD_NOTCHK   = "SD: NOT CHECKED\015\012";
  localparam S_SD_NOCARD   = "SD: NO CARD\015\012";
  localparam S_SD_ERROR    = "SD: ERROR\015\012";
  localparam S_SD_OK       = "SD: OK\015\012";
  localparam S_COPYING     = "COPYING BOOT.BPUN TO TEST.TXT\015\012";
  localparam S_COPY_DONE   = "COPY DONE\015\012";
  localparam S_ERR_NOTGT   = "ERROR: TARGET FILE NOT ON CARD\015\012";
  localparam S_ERR_SMALL   = "ERROR: NO CONTIGUOUS FREE SPACE\015\012";
  localparam S_ERR_WRITE   = "ERROR: SD WRITE FAILED\015\012";
  localparam S_BLK_WRITING = "WRITING PATTERN TO 1KW BLOCK 1\015\012";
  localparam S_BLK_DONE    = "BLOCK 1 UPDATED\015\012";
  localparam S_ERR_RANGE   = "ERROR: BLOCK OUT OF RANGE\015\012";
  localparam S_HELP1       = "1 = LIST ROOT DIR + CARD/VOL/FREE MB INFO LINE\015\012";
  localparam S_HELP2       = "2 = DUMP WHOLE FILE VIA 64 KB BUFFER, HEX + OCTAL\015\012";
`ifdef SDFAT_TEST_READONLY
  // 3/4/6/7 are not in this build: an empty string prints nothing, so the
  // help sequence below needs no second form
  localparam S_HELP3       = 8'h00;
  localparam S_HELP4       = 8'h00;
  localparam S_HELP6       = 8'h00;
  localparam S_HELP7       = 8'h00;
`else
  localparam S_HELP3       = "3 = COPY THE FILE OVER TEST.TXT (IN PLACE)\015\012";
  localparam S_HELP4       = "4 = WRITE WORDS 0-1023 TO 1KW BLOCK 1 OF THE FILE\015\012";
  localparam S_HELP6       = "6 = WRITE SPEED: REWRITE IO.DAT, 1000 X 1KW BLOCKS\015\012";
  localparam S_HELP7       = "7 = READ SPEED: READ IO.DAT BACK (RUN 6 FIRST)\015\012";
`endif
  localparam S_HELP5       = "5 = VALIDATE EVERY FILE CLUSTER CHAIN (FSCK-LITE)\015\012";
  localparam S_HELP8       = "8 = DUMP 1KW BLOCK N OF THE FILE, ANY FILE SIZE\015\012";
  localparam S_HELP9       = "9 = DUMP ABSOLUTE SD SECTOR N, FAT NOT CONSULTED\015\012";
  localparam S_HELP10      = "N = SET FILE NAME: ROOT DIR ONLY, 8.3 LENGTH EXACT\015\012";
  localparam S_HELP11      = "LIST COLUMN 4 = FIRST ABSOLUTE SECTOR OF THE FILE\015\012";
  localparam S_HELP12      = "BLOCK N IS AT FIRST SECTOR + 4*N (FILE CONTIGUOUS)\015\012";
  localparam S_HELP13      = "R = READ A BLOCK RANGE, CHECKSUM ONLY, NO DUMP\015\012";
  localparam S_ASK_BLK     = "BLOCK NUMBER, DECIMAL, THEN CR: ";
  localparam S_ASK_STA     = "START BLOCK, DECIMAL, THEN CR: ";
  localparam S_ASK_CNT     = "BLOCK COUNT, DECIMAL, THEN CR: ";
  // The range summary is printed in HEX throughout (each label is followed
  // by 8 hex digits from the top level's hex printer): one number format
  // for every field keeps the console reader honest, and CYCLES is a raw
  // 27 MHz count anyway - seconds = CYCLES / 27000000.
  localparam S_R_RUN       = "READING BLOCK RANGE AT SECTOR ";
  localparam S_R_AT        = "AT BLOCK ";
  localparam S_R_BLOCKS    = "BLOCKS READ ";
  localparam S_R_SECS      = "SECTORS READ ";
  localparam S_R_CHK       = "CHECKSUM ";
  localparam S_R_CYC       = "CYCLES ";
  localparam S_R_PASS      = "RESULT: PASS\015\012";
  localparam S_R_FAIL      = "RESULT: FAIL\015\012";
  localparam S_ASK_SEC     = "ABSOLUTE SECTOR, DECIMAL, THEN CR: ";
  localparam S_ASK_NAME    = "FILE NAME, THEN CR: ";
  localparam S_ERR_NUM     = "ERROR: NO DIGITS ENTERED\015\012";
  localparam S_AT_SEC      = "AT SECTOR ";
  localparam S_NAME_SET    = "FILE NAME SET\015\012";
  localparam S_ERR_IOSZ    = "ERROR: IO.DAT WRONG SIZE - RUN 6 FIRST\015\012";
  localparam S_IOW_RUN     = "WRITING IO.DAT\015\012";
  localparam S_IOR_RUN     = "READING IO.DAT\015\012";
  localparam S_SCANNING    = "SCANNING FAT FOR FREE SPACE...\015\012";
  localparam S_ERR_READ    = "ERROR: SD READ FAILED\015\012";
  localparam S_ERR_FATRD   = "ERROR: FAT READ FAILED\015\012";
  localparam S_ERR_FATCOR  = "ERROR: FAT CHAIN CORRUPT\015\012";

  // pick character idx (0-based) out of a string literal; 0 terminates
  function [7:0] strchar(input [8*64-1:0] s, input integer len, input integer i);
    strchar = (i < len) ? s[8*(len-1-i)+:8] : 8'h00;
  endfunction

  reg [5:0] msg_r;
  reg [5:0] idx;

  reg [7:0] ch;
  always @(*) begin
    case (msg_r)
      MSG_BANNER:      ch = strchar({8'b0, S_BANNER}, $bits(S_BANNER)/8, idx);
      MSG_CARD_SDV1:   ch = strchar({8'b0, S_CARD_SDV1}, $bits(S_CARD_SDV1)/8, idx);
      MSG_CARD_SDV2:   ch = strchar({8'b0, S_CARD_SDV2}, $bits(S_CARD_SDV2)/8, idx);
      MSG_CARD_SDHC:   ch = strchar({8'b0, S_CARD_SDHC}, $bits(S_CARD_SDHC)/8, idx);
      MSG_FS_FAT16:    ch = strchar({8'b0, S_FS_FAT16}, $bits(S_FS_FAT16)/8, idx);
      MSG_FS_FAT32:    ch = strchar({8'b0, S_FS_FAT32}, $bits(S_FS_FAT32)/8, idx);
      MSG_FS_UNKNOWN:  ch = strchar({8'b0, S_FS_UNKNOWN}, $bits(S_FS_UNKNOWN)/8, idx);
      MSG_FILE_FOUND:  ch = strchar({8'b0, S_FILE_FOUND}, $bits(S_FILE_FOUND)/8, idx);
      MSG_FILE_NOTFND: ch = strchar({8'b0, S_FILE_NOTFND}, $bits(S_FILE_NOTFND)/8, idx);
      MSG_ERR_CARD_TO: ch = strchar({8'b0, S_ERR_CARD_TO}, $bits(S_ERR_CARD_TO)/8, idx);
      MSG_ERR_SCAN_TO: ch = strchar({8'b0, S_ERR_SCAN_TO}, $bits(S_ERR_SCAN_TO)/8, idx);
      MSG_ERR_TRUNC:   ch = strchar({8'b0, S_ERR_TRUNC}, $bits(S_ERR_TRUNC)/8, idx);
      MSG_MENU:        ch = strchar({8'b0, S_MENU}, $bits(S_MENU)/8, idx);
      MSG_NOTIMPL:     ch = strchar({8'b0, S_NOTIMPL}, $bits(S_NOTIMPL)/8, idx);
      MSG_SD_NOTCHK:   ch = strchar({8'b0, S_SD_NOTCHK}, $bits(S_SD_NOTCHK)/8, idx);
      MSG_SD_NOCARD:   ch = strchar({8'b0, S_SD_NOCARD}, $bits(S_SD_NOCARD)/8, idx);
      MSG_SD_ERROR:    ch = strchar({8'b0, S_SD_ERROR}, $bits(S_SD_ERROR)/8, idx);
      MSG_SD_OK:       ch = strchar({8'b0, S_SD_OK}, $bits(S_SD_OK)/8, idx);
      MSG_COPYING:     ch = strchar({8'b0, S_COPYING}, $bits(S_COPYING)/8, idx);
      MSG_COPY_DONE:   ch = strchar({8'b0, S_COPY_DONE}, $bits(S_COPY_DONE)/8, idx);
      MSG_ERR_NOTGT:   ch = strchar({8'b0, S_ERR_NOTGT}, $bits(S_ERR_NOTGT)/8, idx);
      MSG_ERR_SMALL:   ch = strchar({8'b0, S_ERR_SMALL}, $bits(S_ERR_SMALL)/8, idx);
      MSG_ERR_WRITE:   ch = strchar({8'b0, S_ERR_WRITE}, $bits(S_ERR_WRITE)/8, idx);
      MSG_BLK_WRITING: ch = strchar({8'b0, S_BLK_WRITING}, $bits(S_BLK_WRITING)/8, idx);
      MSG_BLK_DONE:    ch = strchar({8'b0, S_BLK_DONE}, $bits(S_BLK_DONE)/8, idx);
      MSG_ERR_RANGE:   ch = strchar({8'b0, S_ERR_RANGE}, $bits(S_ERR_RANGE)/8, idx);
      MSG_HELP1:       ch = strchar({8'b0, S_HELP1}, $bits(S_HELP1)/8, idx);
      MSG_HELP2:       ch = strchar({8'b0, S_HELP2}, $bits(S_HELP2)/8, idx);
      MSG_HELP3:       ch = strchar({8'b0, S_HELP3}, $bits(S_HELP3)/8, idx);
      MSG_HELP4:       ch = strchar({8'b0, S_HELP4}, $bits(S_HELP4)/8, idx);
      MSG_HELP5:       ch = strchar({8'b0, S_HELP5}, $bits(S_HELP5)/8, idx);
      MSG_HELP6:       ch = strchar({8'b0, S_HELP6}, $bits(S_HELP6)/8, idx);
      MSG_HELP7:       ch = strchar({8'b0, S_HELP7}, $bits(S_HELP7)/8, idx);
      MSG_ERR_IOSZ:    ch = strchar({8'b0, S_ERR_IOSZ}, $bits(S_ERR_IOSZ)/8, idx);
      MSG_IOW_RUN:     ch = strchar({8'b0, S_IOW_RUN}, $bits(S_IOW_RUN)/8, idx);
      MSG_IOR_RUN:     ch = strchar({8'b0, S_IOR_RUN}, $bits(S_IOR_RUN)/8, idx);
      MSG_SCANNING:    ch = strchar({8'b0, S_SCANNING}, $bits(S_SCANNING)/8, idx);
      MSG_ERR_READ:    ch = strchar({8'b0, S_ERR_READ}, $bits(S_ERR_READ)/8, idx);
      MSG_ERR_FATRD:   ch = strchar({8'b0, S_ERR_FATRD}, $bits(S_ERR_FATRD)/8, idx);
      MSG_ERR_FATCOR:  ch = strchar({8'b0, S_ERR_FATCOR}, $bits(S_ERR_FATCOR)/8, idx);
      MSG_MENU2:       ch = strchar({8'b0, S_MENU2}, $bits(S_MENU2)/8, idx);
      MSG_ASK_BLK:     ch = strchar({8'b0, S_ASK_BLK}, $bits(S_ASK_BLK)/8, idx);
      MSG_ASK_SEC:     ch = strchar({8'b0, S_ASK_SEC}, $bits(S_ASK_SEC)/8, idx);
      MSG_ASK_NAME:    ch = strchar({8'b0, S_ASK_NAME}, $bits(S_ASK_NAME)/8, idx);
      MSG_ERR_NUM:     ch = strchar({8'b0, S_ERR_NUM}, $bits(S_ERR_NUM)/8, idx);
      MSG_AT_SEC:      ch = strchar({8'b0, S_AT_SEC}, $bits(S_AT_SEC)/8, idx);
      MSG_NAME_SET:    ch = strchar({8'b0, S_NAME_SET}, $bits(S_NAME_SET)/8, idx);
      MSG_HELP8:       ch = strchar({8'b0, S_HELP8}, $bits(S_HELP8)/8, idx);
      MSG_HELP9:       ch = strchar({8'b0, S_HELP9}, $bits(S_HELP9)/8, idx);
      MSG_HELP10:      ch = strchar({8'b0, S_HELP10}, $bits(S_HELP10)/8, idx);
      MSG_HELP11:      ch = strchar({8'b0, S_HELP11}, $bits(S_HELP11)/8, idx);
      MSG_HELP12:      ch = strchar({8'b0, S_HELP12}, $bits(S_HELP12)/8, idx);
      MSG_HELP13:      ch = strchar({8'b0, S_HELP13}, $bits(S_HELP13)/8, idx);
      MSG_ASK_STA:     ch = strchar({8'b0, S_ASK_STA}, $bits(S_ASK_STA)/8, idx);
      MSG_ASK_CNT:     ch = strchar({8'b0, S_ASK_CNT}, $bits(S_ASK_CNT)/8, idx);
      MSG_R_RUN:       ch = strchar({8'b0, S_R_RUN}, $bits(S_R_RUN)/8, idx);
      MSG_R_AT:        ch = strchar({8'b0, S_R_AT}, $bits(S_R_AT)/8, idx);
      MSG_R_BLOCKS:    ch = strchar({8'b0, S_R_BLOCKS}, $bits(S_R_BLOCKS)/8, idx);
      MSG_R_SECS:      ch = strchar({8'b0, S_R_SECS}, $bits(S_R_SECS)/8, idx);
      MSG_R_CHK:       ch = strchar({8'b0, S_R_CHK}, $bits(S_R_CHK)/8, idx);
      MSG_R_CYC:       ch = strchar({8'b0, S_R_CYC}, $bits(S_R_CYC)/8, idx);
      MSG_R_PASS:      ch = strchar({8'b0, S_R_PASS}, $bits(S_R_PASS)/8, idx);
      MSG_R_FAIL:      ch = strchar({8'b0, S_R_FAIL}, $bits(S_R_FAIL)/8, idx);
      default:         ch = 8'h00;
    endcase
  end

  // Byte pump: hand ch to uart_tx, one handshake per character
  localparam P_IDLE = 2'd0;
  localparam P_SEND = 2'd1;
  localparam P_GAP  = 2'd2;  // one cycle for tx_busy to assert

  reg [1:0] pstate;

  assign busy = (pstate != P_IDLE);

  always @(posedge clk) begin
    if (!rst_n) begin
      pstate   <= P_IDLE;
      tx_valid <= 1'b0;
      tx_data  <= 0;
      idx      <= 0;
      msg_r    <= MSG_BANNER;
    end else begin
      tx_valid <= 1'b0;
      case (pstate)
        P_IDLE:
        if (start) begin
          msg_r  <= msg;
          idx    <= 0;
          pstate <= P_SEND;
        end
        P_SEND:
        if (ch == 8'h00) begin
          pstate <= P_IDLE;
        end else if (!tx_busy) begin
          tx_valid <= 1'b1;
          tx_data  <= ch;
          idx      <= idx + 1;
          pstate   <= P_GAP;
        end
        P_GAP: pstate <= P_SEND;
        default: pstate <= P_IDLE;
      endcase
    end
  end

endmodule