Skip to content

hex_dumper

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

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

Module hierarchy - All modules

hex_dumper symbol

Schematic

Drawn from the Verilog: no build top uses this module, so it was elaborated from its own file with no defines and default parameters. Sub-modules are boxes (click the picture to open it full size; there every sub-module box links to its page, and every wire shows its Verilog name).

hex_dumper schematic

Description

Hex/octal dumper for the SD-FAT test Streams the contents of a byte buffer (BRAM read port, 1-cycle read latency) as an ASCII dump through the shared uart_tx: 000000: 8D 0A 30 30 36 30 30 30 30 8D 0A B1 36 B4 33 B1 ... (16 bytes/line, extra space after byte 8, a space after EVERY byte incl. the last) OCTAL WORDS AFTER LEADER: 105015 030060 ... (first 8 words from the first NONZERO byte - BPUN tapes begin with a zero leader) LENGTH: 000000002342 BYTES DONE The octal line prints the first min(8, length/2) 16-bit words, MSB-first byte order (BPUN fields are 16-bit MSB-first words). All line ends are CR LF. Plain ASCII only. NOTE: \r is not a legal Verilog string escape - CR must be octal \015. Last reviewed: 10-JUL-2026 Ronny Hansen

Parameters

Parameter Default
ADDR_W 16 // buffer address width; length is ADDR_W+1 bits

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 [ADDR_W:0] length number of bytes to dump (may be 0)
output 1 busy
output [ADDR_W-1:0] mem_addr
input [7:0] mem_data
output [7:0] tx_data
output 1 tx_valid
input 1 tx_busy

Verilog source

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

Show the Verilog of hex_dumper (401 lines)
/****************************************************************************
** Hex/octal dumper for the SD-FAT test                                    **
**                                                                         **
** Streams the contents of a byte buffer (BRAM read port, 1-cycle read    **
** latency) as an ASCII dump through the shared uart_tx:                   **
**                                                                         **
**   000000: 8D 0A 30 30 36 30 30 30  30 8D 0A B1 36 B4 33 B1             **
**   ...                       (16 bytes/line, extra space after byte 8,   **
**                              a space after EVERY byte incl. the last)   **
**   OCTAL WORDS AFTER LEADER: 105015 030060 ...  (first 8 words from    **
**                                the first NONZERO byte - BPUN tapes     **
**                                begin with a zero leader)                **
**   LENGTH: 000000002342 BYTES                                            **
**   DONE                                                                  **
**                                                                         **
** The octal line prints the first min(8, length/2) 16-bit words,          **
** MSB-first byte order (BPUN fields are 16-bit MSB-first words).          **
** All line ends are CR LF. Plain ASCII only.                              **
**                                                                         **
** NOTE: \r is not a legal Verilog string escape - CR must be octal \015.  **
**                                                                         **
** Last reviewed: 10-JUL-2026                                              **
** Ronny Hansen                                                            **
*****************************************************************************/

module hex_dumper #(
    parameter ADDR_W = 16  // buffer address width; length is ADDR_W+1 bits
) (
    input clk,
    input rst_n,

    input                 start,   // 1-cycle pulse; only when busy=0
    input  [ADDR_W:0]     length,  // number of bytes to dump (may be 0)
    output                busy,

    // buffer read port (registered BRAM: data valid 1 clk after addr)
    output reg [ADDR_W-1:0] mem_addr,
    input      [7:0]        mem_data,

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

  localparam S_OCT  = "OCTAL WORDS AFTER LEADER: ";
  localparam S_LEN  = "LENGTH: ";
  localparam S_BYT  = " BYTES\015\012";
  localparam S_DONE = "DONE\015\012";
  localparam S_CRLF = "\015\012";
  localparam S_COL  = ": ";

  function [7:0] hexd(input [3:0] n);
    hexd = (n < 4'd10) ? (8'h30 + {4'b0, n}) : (8'h37 + {4'b0, n});  // '0'-'9','A'-'F'
  endfunction

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

  // powers of ten for the 12-digit decimal LENGTH field (needs 40 bits)
  function [39:0] pow10(input [3:0] i);
    case (i)
      4'd0:  pow10 = 40'd1;
      4'd1:  pow10 = 40'd10;
      4'd2:  pow10 = 40'd100;
      4'd3:  pow10 = 40'd1000;
      4'd4:  pow10 = 40'd10000;
      4'd5:  pow10 = 40'd100000;
      4'd6:  pow10 = 40'd1000000;
      4'd7:  pow10 = 40'd10000000;
      4'd8:  pow10 = 40'd100000000;
      4'd9:  pow10 = 40'd1000000000;
      4'd10: pow10 = 40'd10000000000;
      default: pow10 = 40'd100000000000;
    endcase
  endfunction

  // ------------------------------------------------------------------ FSM
  localparam ST_IDLE       = 5'd0;
  localparam ST_LINE       = 5'd1;   // decide: next dump line or octal section
  localparam ST_ADDR_DIG   = 5'd2;   // 6 hex digits of the line offset
  localparam ST_COLON      = 5'd3;   // ": "
  localparam ST_BYTE_SET   = 5'd4;   // present buffer address
  localparam ST_BYTE_WAIT  = 5'd5;   // BRAM read latency
  localparam ST_BYTE_HI    = 5'd6;
  localparam ST_BYTE_LO    = 5'd7;
  localparam ST_BYTE_SP    = 5'd8;   // space after the byte
  localparam ST_BYTE_SP2   = 5'd9;   // extra space after byte 8
  localparam ST_OCT_HDR    = 5'd10;
  localparam ST_OCT_SET_HI = 5'd11;
  localparam ST_OCT_WAIT_HI= 5'd12;
  localparam ST_OCT_LAT_HI = 5'd13;
  localparam ST_OCT_SET_LO = 5'd14;
  localparam ST_OCT_WAIT_LO= 5'd15;
  localparam ST_OCT_LAT_LO = 5'd16;
  localparam ST_OCT_DIG    = 5'd17;  // 6 octal digits of the word
  localparam ST_OCT_SP     = 5'd18;
  localparam ST_LINE_CRLF   = 5'd19;
  localparam ST_LEN_HDR    = 5'd20;
  localparam ST_LEN_DIG    = 5'd21;  // 12 decimal digits, repeated subtraction
  localparam ST_LEN_TAIL   = 5'd22;  // " BYTES\r\n"
  localparam ST_DONE_STR   = 5'd23;
  localparam ST_STR        = 5'd24;  // generic string emitter (str_sel)
  localparam ST_SEND       = 5'd25;  // char handshake to uart_tx
  localparam ST_GAP        = 5'd26;  // one cycle for tx_busy to assert
  localparam ST_OSCAN_SET  = 5'd27;  // find the first nonzero byte (BPUN
  localparam ST_OSCAN_WAIT = 5'd28;  // tapes start with a zero leader -
  localparam ST_OSCAN_CHK  = 5'd29;  // octal words start after it)

  // string selector for ST_STR
  localparam SEL_OCT  = 3'd0;
  localparam SEL_LEN  = 3'd1;
  localparam SEL_BYT  = 3'd2;
  localparam SEL_DONE = 3'd3;
  localparam SEL_CRLF = 3'd4;
  localparam SEL_COL  = 3'd5;

  reg [4:0] state, ret_state, str_ret;
  reg [2:0] str_sel;
  reg [5:0] sidx;      // index into the selected string

  reg [ADDR_W:0] len_r;    // latched length
  reg [ADDR_W:0] offs;     // current line offset
  reg [ADDR_W:0] obase;    // octal section: first byte after the zero leader
  reg [4:0]      bidx;     // byte index within the line (0..15)
  reg [2:0]      digidx;   // hex/octal digit counter
  reg [15:0]     word;     // octal section: current 16-bit word
  reg [3:0]      widx;     // octal section: word index (0..7)
  reg [3:0]      wcnt;     // octal section: number of words to print
  reg [39:0]     decval;   // decimal conversion remainder
  reg [3:0]      decdig;   // current decimal digit value
  reg [3:0]      decpow;   // current power-of-ten index (11..0)

  wire [23:0] offs24 = {{(24-ADDR_W-1){1'b0}}, offs};
  wire [ADDR_W:0] orem = len_r - obase;  // bytes after the zero leader

  assign busy = (state != ST_IDLE);

  // character of the selected string at sidx
  reg [7:0] str_ch;
  always @(*) begin
    case (str_sel)
      SEL_OCT:  str_ch = strchar({8'b0, S_OCT}, $bits(S_OCT)/8, sidx);
      SEL_LEN:  str_ch = strchar({8'b0, S_LEN}, $bits(S_LEN)/8, sidx);
      SEL_BYT:  str_ch = strchar({8'b0, S_BYT}, $bits(S_BYT)/8, sidx);
      SEL_DONE: str_ch = strchar({8'b0, S_DONE}, $bits(S_DONE)/8, sidx);
      SEL_CRLF: str_ch = strchar({8'b0, S_CRLF}, $bits(S_CRLF)/8, sidx);
      default:  str_ch = strchar({8'b0, S_COL}, $bits(S_COL)/8, sidx);
    endcase
  end

  always @(posedge clk) begin
    if (!rst_n) begin
      state     <= ST_IDLE;
      ret_state <= ST_IDLE;
      str_ret   <= ST_IDLE;
      str_sel   <= SEL_CRLF;
      sidx      <= 0;
      tx_valid  <= 1'b0;
      tx_data   <= 0;
      mem_addr  <= 0;
      len_r     <= 0;
      offs      <= 0;
      obase     <= 0;
      bidx      <= 0;
      digidx    <= 0;
      word      <= 0;
      widx      <= 0;
      wcnt      <= 0;
      decval    <= 0;
      decdig    <= 0;
      decpow    <= 0;
    end else begin
      tx_valid <= 1'b0;
      case (state)
        ST_IDLE:
        if (start) begin
          len_r  <= length;
          offs   <= 0;
          digidx <= 0;
          state  <= ST_LINE;
        end

        // ---- hex dump lines -------------------------------------------
        ST_LINE:
        if (offs >= len_r) begin
          obase <= 0;
          state <= (len_r == 0) ? ST_OCT_HDR : ST_OSCAN_SET;
          if (len_r == 0) begin
            str_sel <= SEL_OCT;
            sidx    <= 0;
            str_ret <= ST_OCT_HDR;
            state   <= ST_STR;
          end
        end else begin
          digidx <= 0;
          state  <= ST_ADDR_DIG;
        end

        ST_ADDR_DIG: begin
          tx_data   <= hexd(offs24[23-4*digidx-:4]);
          ret_state <= (digidx == 3'd5) ? ST_COLON : ST_ADDR_DIG;
          digidx    <= digidx + 3'd1;
          state     <= ST_SEND;
        end

        ST_COLON: begin
          str_sel <= SEL_COL;
          sidx    <= 0;
          str_ret <= ST_BYTE_SET;
          bidx    <= 0;
          state   <= ST_STR;
        end

        ST_BYTE_SET: begin
          mem_addr <= offs[ADDR_W-1:0] + {{(ADDR_W-5){1'b0}}, bidx};
          state    <= ST_BYTE_WAIT;
        end

        ST_BYTE_WAIT: state <= ST_BYTE_HI;

        ST_BYTE_HI: begin
          tx_data   <= hexd(mem_data[7:4]);
          ret_state <= ST_BYTE_LO;
          state     <= ST_SEND;
        end

        ST_BYTE_LO: begin
          tx_data   <= hexd(mem_data[3:0]);
          ret_state <= ST_BYTE_SP;
          state     <= ST_SEND;
        end

        ST_BYTE_SP: begin
          // space after byte bidx; then: end of line (16 bytes or end of
          // data) -> CRLF; after byte 8 -> one extra space; else next byte
          tx_data <= " ";
          if ((bidx == 5'd15) || (offs + {{(ADDR_W-4){1'b0}}, bidx} + 1 >= len_r))
            ret_state <= ST_LINE_CRLF;
          else if (bidx == 5'd7)
            ret_state <= ST_BYTE_SP2;
          else
            ret_state <= ST_BYTE_SET;
          bidx  <= bidx + 5'd1;
          state <= ST_SEND;
        end

        ST_BYTE_SP2: begin
          tx_data   <= " ";
          ret_state <= ST_BYTE_SET;
          state     <= ST_SEND;
        end

        // ---- find the end of the zero leader ----------------------------
        ST_OSCAN_SET: begin
          mem_addr <= obase[ADDR_W-1:0];
          state    <= ST_OSCAN_WAIT;
        end

        ST_OSCAN_WAIT: state <= ST_OSCAN_CHK;

        ST_OSCAN_CHK:
        if (mem_data != 8'h00 || obase + 1 >= len_r) begin
          str_sel <= SEL_OCT;
          sidx    <= 0;
          str_ret <= ST_OCT_HDR;
          state   <= ST_STR;
        end else begin
          obase <= obase + 1;
          state <= ST_OSCAN_SET;
        end

        // ---- octal words section --------------------------------------
        ST_OCT_HDR: begin
          // words available after the leader: min(8, remaining/2)
          wcnt <= (orem >= 16) ? 4'd8 : orem[3:1];
          widx <= 0;
          state <= ST_OCT_SET_HI;
        end

        ST_OCT_SET_HI:
        if (widx >= wcnt) begin
          str_sel <= SEL_CRLF;
          sidx    <= 0;
          str_ret <= ST_LEN_HDR;
          state   <= ST_STR;
        end else begin
          mem_addr <= obase[ADDR_W-1:0] + {{(ADDR_W-5){1'b0}}, widx, 1'b0};
          state    <= ST_OCT_WAIT_HI;
        end

        ST_OCT_WAIT_HI: state <= ST_OCT_LAT_HI;

        ST_OCT_LAT_HI: begin
          word[15:8] <= mem_data;
          mem_addr   <= obase[ADDR_W-1:0] + {{(ADDR_W-5){1'b0}}, widx, 1'b1};
          state      <= ST_OCT_WAIT_LO;
        end

        ST_OCT_WAIT_LO: state <= ST_OCT_LAT_LO;

        ST_OCT_LAT_LO: begin
          word[7:0] <= mem_data;
          digidx    <= 0;
          state     <= ST_OCT_DIG;
        end

        ST_OCT_DIG: begin
          case (digidx)
            3'd0: tx_data <= 8'h30 + {7'b0, word[15]};
            3'd1: tx_data <= 8'h30 + {5'b0, word[14:12]};
            3'd2: tx_data <= 8'h30 + {5'b0, word[11:9]};
            3'd3: tx_data <= 8'h30 + {5'b0, word[8:6]};
            3'd4: tx_data <= 8'h30 + {5'b0, word[5:3]};
            default: tx_data <= 8'h30 + {5'b0, word[2:0]};
          endcase
          ret_state <= (digidx == 3'd5) ? ST_OCT_SP : ST_OCT_DIG;
          digidx    <= digidx + 3'd1;
          state     <= ST_SEND;
        end

        ST_OCT_SP: begin
          tx_data   <= " ";
          widx      <= widx + 4'd1;
          ret_state <= ST_OCT_SET_HI;
          state     <= ST_SEND;
        end

        ST_LINE_CRLF: begin  // end of a dump line
          str_sel <= SEL_CRLF;
          sidx    <= 0;
          str_ret <= ST_LINE;
          offs    <= offs + 16;
          state   <= ST_STR;
        end

        // ---- LENGTH line ----------------------------------------------
        ST_LEN_HDR: begin
          str_sel <= SEL_LEN;
          sidx    <= 0;
          str_ret <= ST_LEN_DIG;
          decval  <= {{(40-ADDR_W-1){1'b0}}, len_r};
          decpow  <= 4'd11;
          decdig  <= 4'd0;
          state   <= ST_STR;
        end

        ST_LEN_DIG:
        if (decval >= pow10(decpow)) begin
          decval <= decval - pow10(decpow);
          decdig <= decdig + 4'd1;
        end else begin
          tx_data <= 8'h30 + {4'b0, decdig};
          decdig  <= 4'd0;
          if (decpow == 4'd0) begin
            str_sel   <= SEL_BYT;
            sidx      <= 0;
            str_ret   <= ST_DONE_STR;
            ret_state <= ST_STR;
          end else begin
            decpow    <= decpow - 4'd1;
            ret_state <= ST_LEN_DIG;
          end
          state <= ST_SEND;
        end

        ST_DONE_STR: begin
          str_sel <= SEL_DONE;
          sidx    <= 0;
          str_ret <= ST_IDLE;
          state   <= ST_STR;
        end

        // ---- generic string emitter ------------------------------------
        ST_STR:
        if (str_ch == 8'h00) begin
          state <= str_ret;
        end else begin
          tx_data   <= str_ch;
          sidx      <= sidx + 6'd1;
          ret_state <= ST_STR;
          state     <= ST_SEND;
        end

        // ---- single-character handshake to uart_tx ----------------------
        ST_SEND:
        if (!tx_busy) begin
          tx_valid <= 1'b1;
          state    <= ST_GAP;
        end

        ST_GAP: state <= ret_state;

        default: state <= ST_IDLE;
      endcase
    end
  end

endmodule