Skip to content

nd_storage_tape_adapter

Source: Verilog/SD-FAT/circuit/nd_storage_tape_adapter.v

Where it sits (Simulation): ND120_TOP > nd_storage_devices > nd_storage_tape_adapter - instance path: TAPE_SDFAT_SOURCE.gen_tape.u_tape_adapter

Used in: nd_storage_devices (Simulation, Tang, Nexys, QMTECH), nd_storage_mega65_devices (MEGA65 R6, MEGA65 R3), nd_storage_mister_devices (MiSTer)

Contains: no other modules.

Module hierarchy - All modules

nd_storage_tape_adapter symbol

Schematic

Drawn from the Verilog: the yosys netlist of the Simulation (Verilator) build, instance TAPE_SDFAT_SOURCE.gen_tape.u_tape_adapter. 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).

nd_storage_tape_adapter schematic

Description

nd_storage_tape_adapter - byte-stream adapter for the paper-tape reader Design doc: Verilog/docs/nd-storage-design.md section 2.5; spec Verilog/docs/nd-storage-interface-spec.md section 5 (acceptance test 4). Sits between ND_TAPE_400's byte source port (byte_req / byte_valid / byte_data / source_rewind - pin-for-pin, see Verilog/ND-BUS-DEVICES/TAPE-400/circuit/ND_TAPE_400.v) and ONE nd_storage client port. Single clock: clk_cpu (the client-port domain - the CDC lives inside nd_storage). Behavior: - keeps a 32-bit byte position s_bptr; block = s_bptr[26:11], word-in-block = s_bptr[10:1] - on byte_req: position past EOF (s_bptr >= c_size_bytes) or file not open -> SILENCE (no byte_valid, no client-port traffic - the tape's ready-for-transfer flag stays low, the C-model EOF behavior). The zero-padded slot tail past EOF is never served. - block hit (s_have_blk and s_cur_blk matches) -> serve from the local 1024x16 block buffer: byte_valid pulse with byte 2w = word[15:8], byte 2w+1 = word[7:0] (big-endian word order, design doc 4.1 normative) - miss -> one c_req block READ into the local buffer, then serve - source_rewind: position back to 0, buffer invalidated; an in-flight fetch is discarded on completion. NO card access - the image lives in SDRAM. - c_err on a fetch -> drop s_have_blk, stay silent (tape runout semantics); the next byte_req simply retries the fetch. WHY THIS ONE CANNOT TELL THE GUEST WHY IT STOPPED: the byte source port is byte_req / byte_valid / byte_data / source_rewind and nothing else - a real ND-400 paper tape reader has no register in which "the SD card is missing" could be expressed, and inventing one would put a signal on the card that its manual does not define. So the guest still sees exactly the runout silence it saw before, and the reason is instead published on the STICKY diagnostic pair fault / fault_code (nd_storage_status.vh), which no ND logic reads - it exists for a testbench, a probe or a board LED. Cleared by source_rewind and reset. Without it, "the card fell out" and "you reached the end of the tape" are the same event from every observation point. READ-ONLY by construction: c_wr is tied 0 and c_buf_rdata is tied 0, so the step-6 FLAG (never write the partial tail block of a file whose size is not a 2048-byte multiple) holds trivially - there is no write path at all. open_start is a pulse from the board/boot logic; it is passed through as a c_open_req pulse (nd_storage ignores it while the port is busy). Last reviewed: 11-JUL-2026 Ronny Hansen

Ports

Direction Width Name Description
input 1 clk_cpu
input 1 rst_n (active low)
input 1 byte_req 1-cycle pulse: fetch next byte
output 1 byte_valid 1-cycle pulse: byte_data is the byte
output [7:0] byte_data
input 1 source_rewind 1-cycle pulse: back to byte 0
output 1 fault
output [3:0] fault_code
input 1 open_start 1-cycle pulse: (re)open the file
output 1 c_open_req
input 1 c_open_ok
input 1 c_open_err
input [31:0] c_size_bytes
output 1 c_req
output 1 c_wr tied 0: tape is READ-ONLY
output [15:0] c_block
input 1 c_busy
input 1 c_done 1-cycle pulse
input 1 c_err valid with c_done
input [3:0] c_err_code valid with c_done when c_err
input [9:0] c_buf_addr
input [15:0] c_buf_wdata
input 1 c_buf_we
output [15:0] c_buf_rdata never used (no write path)

Verilog source

Verilog/SD-FAT/circuit/nd_storage_tape_adapter.v on GitHub.

Show the Verilog of nd_storage_tape_adapter (228 lines)
/****************************************************************************
** nd_storage_tape_adapter - byte-stream adapter for the paper-tape reader **
**                                                                         **
** Design doc: Verilog/docs/nd-storage-design.md section 2.5; spec         **
** Verilog/docs/nd-storage-interface-spec.md section 5 (acceptance test 4).**
**                                                                         **
** Sits between ND_TAPE_400's byte source port (byte_req / byte_valid /    **
** byte_data / source_rewind - pin-for-pin, see                            **
** Verilog/ND-BUS-DEVICES/TAPE-400/circuit/ND_TAPE_400.v) and ONE          **
** nd_storage client port. Single clock: clk_cpu (the client-port domain   **
** - the CDC lives inside nd_storage).                                     **
**                                                                         **
** Behavior:                                                               **
**   - keeps a 32-bit byte position s_bptr; block = s_bptr[26:11],         **
**     word-in-block = s_bptr[10:1]                                        **
**   - on byte_req: position past EOF (s_bptr >= c_size_bytes) or file     **
**     not open -> SILENCE (no byte_valid, no client-port traffic - the    **
**     tape's ready-for-transfer flag stays low, the C-model EOF           **
**     behavior). The zero-padded slot tail past EOF is never served.      **
**   - block hit (s_have_blk and s_cur_blk matches) -> serve from the      **
**     local 1024x16 block buffer: byte_valid pulse with                   **
**     byte 2w = word[15:8], byte 2w+1 = word[7:0] (big-endian word        **
**     order, design doc 4.1 normative)                                    **
**   - miss -> one c_req block READ into the local buffer, then serve      **
**   - source_rewind: position back to 0, buffer invalidated; an           **
**     in-flight fetch is discarded on completion. NO card access - the    **
**     image lives in SDRAM.                                               **
**   - c_err on a fetch -> drop s_have_blk, stay silent (tape runout       **
**     semantics); the next byte_req simply retries the fetch.             **
**                                                                         **
** WHY THIS ONE CANNOT TELL THE GUEST WHY IT STOPPED: the byte source      **
** port is byte_req / byte_valid / byte_data / source_rewind and nothing   **
** else - a real ND-400 paper tape reader has no register in which "the    **
** SD card is missing" could be expressed, and inventing one would put a   **
** signal on the card that its manual does not define. So the guest still  **
** sees exactly the runout silence it saw before, and the reason is        **
** instead published on the STICKY diagnostic pair fault / fault_code      **
** (nd_storage_status.vh), which no ND logic reads - it exists for a       **
** testbench, a probe or a board LED. Cleared by source_rewind and reset.  **
** Without it, "the card fell out" and "you reached the end of the tape"   **
** are the same event from every observation point.                        **
**                                                                         **
** READ-ONLY by construction: c_wr is tied 0 and c_buf_rdata is tied 0,    **
** so the step-6 FLAG (never write the partial tail block of a file whose  **
** size is not a 2048-byte multiple) holds trivially - there is no write   **
** path at all.                                                            **
**                                                                         **
** open_start is a pulse from the board/boot logic; it is passed through   **
** as a c_open_req pulse (nd_storage ignores it while the port is busy).   **
**                                                                         **
** Last reviewed: 11-JUL-2026                                              **
** Ronny Hansen                                                            **
*****************************************************************************/

`include "nd_storage_status.vh"

module nd_storage_tape_adapter (
    input wire clk_cpu,
    input wire rst_n,

    // Byte source port (to ND_TAPE_400, pin-for-pin)
    input  wire       byte_req,       // 1-cycle pulse: fetch next byte
    output reg        byte_valid,     // 1-cycle pulse: byte_data is the byte
    output reg  [7:0] byte_data,
    input  wire       source_rewind,  // 1-cycle pulse: back to byte 0

    // Sticky diagnostic (NOT part of the ND-400 device contract - see the
    // header). Set the first time a byte request is refused for a reason
    // that is not plain end-of-tape; cleared by rewind and reset.
    output reg        fault,
    output reg  [3:0] fault_code,

    // Board/boot control
    input wire open_start,  // 1-cycle pulse: (re)open the file

    // nd_storage client port (one client, clk_cpu domain)
    output reg         c_open_req,
    input  wire        c_open_ok,
    input  wire        c_open_err,
    input  wire [31:0] c_size_bytes,
    output reg         c_req,
    output wire        c_wr,          // tied 0: tape is READ-ONLY
    output reg  [15:0] c_block,
    input  wire        c_busy,
    input  wire        c_done,        // 1-cycle pulse
    input  wire        c_err,         // valid with c_done
    input  wire [ 3:0] c_err_code,    // valid with c_done when c_err
    input  wire [ 9:0] c_buf_addr,
    input  wire [15:0] c_buf_wdata,
    input  wire        c_buf_we,
    output wire [15:0] c_buf_rdata    // never used (no write path)
);

  assign c_wr        = 1'b0;
  assign c_buf_rdata = 16'd0;

  // Local copy of the current block (BRAM, written by the client port
  // during a fetch, read back one word at a time to serve bytes)
  reg [15:0] s_blkbuf[0:1023];
  always @(posedge clk_cpu) begin
    if (c_buf_we) s_blkbuf[c_buf_addr] <= c_buf_wdata;
  end

  // FSM
  localparam [2:0] A_IDLE = 3'd0,  // wait for a pending byte request
                   A_WAIT = 3'd1,  // block fetch in flight (c_req issued)
                   A_RD1  = 3'd2,  // registered buffer read
                   A_RD2  = 3'd3;  // emit byte_valid

  reg [ 2:0] s_state;
  reg [31:0] s_bptr;      // byte position in the file
  reg [15:0] s_cur_blk;   // block number held in s_blkbuf
  reg        s_have_blk;  // s_blkbuf/s_cur_blk are valid
  reg        s_pend;      // a byte_req is waiting to be answered
  reg        s_drop;      // rewind hit an in-flight fetch: discard it
  reg [15:0] s_word;      // registered buffer read data

  always @(posedge clk_cpu or negedge rst_n) begin
    if (!rst_n) begin
      byte_valid <= 1'b0;
      byte_data  <= 8'd0;
      c_open_req <= 1'b0;
      c_req      <= 1'b0;
      c_block    <= 16'd0;
      s_state    <= A_IDLE;
      s_bptr     <= 32'd0;
      s_cur_blk  <= 16'd0;
      s_have_blk <= 1'b0;
      s_pend     <= 1'b0;
      s_drop     <= 1'b0;
      s_word     <= 16'd0;
      fault      <= 1'b0;
      fault_code <= `NDS_ERR_NONE;
    end else begin
      byte_valid <= 1'b0;
      c_req      <= 1'b0;
      c_open_req <= 1'b0;

      if (open_start) c_open_req <= 1'b1;

      case (s_state)
        A_IDLE: begin
          if (s_pend) begin
            if (!c_open_ok || (s_bptr >= c_size_bytes)) begin
              // EOF / not open: SILENCE - drop the request, no traffic
              s_pend <= 1'b0;
              // ...but say WHY on the diagnostic pair. Running off the end
              // of the tape is normal and is NOT a fault; having no file to
              // read is. (Both are true at once when the open failed, since
              // c_size_bytes is then 0 - so test !c_open_ok first.)
              if (!c_open_ok && !fault) begin
                fault      <= 1'b1;
                fault_code <= `NDS_ERR_NOTOPEN;
              end
            end else if (s_have_blk && (s_cur_blk == s_bptr[26:11])) begin
              s_state <= A_RD1;
            end else if (!c_busy) begin
              c_req   <= 1'b1;
              c_block <= s_bptr[26:11];
              s_state <= A_WAIT;
            end
          end
        end

        A_WAIT: begin
          if (c_done) begin
            if (s_drop) begin
              // rewound while the fetch was in flight: discard
              s_drop     <= 1'b0;
              s_have_blk <= 1'b0;
              s_state    <= A_IDLE;
            end else if (c_err) begin
              // silent to the device, retryable on the next byte_req - but
              // this is a real storage failure, not the end of the tape
              s_have_blk <= 1'b0;
              s_pend     <= 1'b0;
              s_state    <= A_IDLE;
              if (!fault) begin
                fault      <= 1'b1;
                fault_code <= c_err_code;
              end
            end else begin
              s_have_blk <= 1'b1;
              s_cur_blk  <= c_block;
              s_state    <= A_RD1;
            end
          end
        end

        A_RD1: begin
          s_word  <= s_blkbuf[s_bptr[10:1]];
          s_state <= A_RD2;
        end

        A_RD2: begin
          byte_valid <= 1'b1;
          // big-endian word order: even byte = bits 15:8 (design doc 4.1)
          byte_data  <= s_bptr[0] ? s_word[7:0] : s_word[15:8];
          s_bptr     <= s_bptr + 32'd1;
          s_pend     <= 1'b0;
          s_state    <= A_IDLE;
        end

        default: s_state <= A_IDLE;
      endcase

      // latch a byte request (after the case so a same-cycle serve
      // completion cannot eat a new request)
      if (byte_req) s_pend <= 1'b1;

      // rewind wins over everything in this cycle
      if (source_rewind) begin
        s_bptr     <= 32'd0;
        s_have_blk <= 1'b0;
        s_pend     <= 1'b0;
        byte_valid <= 1'b0;
        fault      <= 1'b0;   // new tape pass: the old verdict is stale
        fault_code <= `NDS_ERR_NONE;
        if ((s_state == A_WAIT) && !c_done) begin
          s_drop <= 1'b1;  // stay in A_WAIT until the fetch completes
        end else begin
          s_state <= A_IDLE;
        end
      end
    end
  end

endmodule