Skip to content

nd_storage_mister_devices

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

Where it sits (MiSTer): emu > nd_storage_mister_devices - instance path: STORAGE

Used in: emu (MiSTer)

Contains: nd_storage_disc_adapter x2, nd_storage_floppy_adapter x2, nd_storage_hps, nd_storage_tape_adapter

Module hierarchy - All modules

nd_storage_mister_devices symbol

Schematic

Drawn from the Verilog: the yosys netlist of the MiSTer build, instance STORAGE. 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_mister_devices schematic

Parameters

Parameter Default
BYTE_SWAP 1 // see nd_storage_hps

Ports

Direction Width Name Description
input 1 clk_cpu 20 MHz, the ND-120 CPU/bus/device domain (from pll_cpu.outclk_0)
input 1 rst_cpu_n (active low) Active-low reset, from the board's power-on reset (same net as ND120_CORE.sys_rst_n)
input 1 clk_sys pixel clock, 40.000 MHz for 800x600@60 (same net as nd120_console_mister.clk)
input 1 rst_sys_n (active low) async reset, active low, in the pixel domain (same net as nd120_console_mister.rst_n)
input 1 byte_req pulse: fetch next tape byte (from ND120_CORE.TAPE_BYTE_REQ)
output 1 byte_valid pulse: TAPE_BYTE_DATA is the byte (to ND120_CORE.TAPE_BYTE_VALID)
output [7:0] byte_data
input 1 source_rewind pulse: rewind the tape source (from ND120_CORE.TAPE_REWIND)
output 1 TDISK_FAULT
output [3:0] TDISK_ERR_CODE
input 1 FDISK_REQ
input 1 FDISK_WR
input [15:0] FDISK_LSECT
input [1:0] FDISK_FORMAT
input [1:0] FDISK_DRIVE
input [10:0] FDISK_WORDCOUNT
output 1 FDISK_DONE
output 1 FDISK_ERR
output [3:0] FDISK_ERR_CODE
output [3:0] FDISK_MEDIA_FMT
output [9:0] FDBUF_ADDR
output [15:0] FDBUF_WDATA
output 1 FDBUF_WE
input [15:0] FDBUF_RDATA
input 1 WDISK_START
input 1 WDISK_REQ
input 1 WDISK_WR
input [15:0] WDISK_BLKADDR1 head b15-8, sector b7-0 (from ND_WINCHESTER.disk_blkaddr1)
input [15:0] WDISK_BLKADDR2
input [2:0] WDISK_UNIT only bit 0 is used - two units (from ND_WINCHESTER.disk_unit)
input [10:0] WDISK_WORDCOUNT words in the current chunk (from ND_WINCHESTER.disk_wordcount)
output 1 WDISK_DONE
output 1 WDISK_ERR
output [3:0] WDISK_ERR_CODE
output [9:0] WDBUF_ADDR
output [15:0] WDBUF_WDATA
output 1 WDBUF_WE
input [15:0] WDBUF_RDATA
input [4:0] img_mounted
input 1 img_readonly
input [63:0] img_size
output [5*32-1:0] sd_lba
output [5*6-1:0] sd_blk_cnt
output [4:0] sd_rd
output [4:0] sd_wr
input [4:0] sd_ack
input [12:0] sd_buff_addr
input [15:0] sd_buff_dout
output [15:0] sd_buff_din
input 1 sd_buff_wr
output [4:0] MOUNTED

Verilog source

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

Show the Verilog of nd_storage_mister_devices (272 lines)
`include "nd_storage_status.vh"
/****************************************************************************
** nd_storage_mister_devices - the ND-120 storage subsystem on the MiSTer  **
**                                                                         **
** The MiSTer counterpart of Verilog/SD-FAT/circuit/nd_storage_devices.v:  **
** the same "storage subsystem in a box" with pin-for-pin FDISK_* and FDBUF_*, **
** WDISK_* and WDBUF_* and TAPE byte-source seams matching ND120_CORE, but **
** with nd_storage_hps behind the adapters instead of the SD/FAT stack.    **
** No SD card, no SPI, no FAT: the images are files the user mounts from   **
** the MiSTer OSD, one per slot.                                           **
**                                                                         **
** SLOT MAP (= OSD "S<n>" lines in nd120.sv's CONF_STR, = hps_io VDNUM     **
** index, = nd_storage_hps client):                                        **
**   0  floppy drive 0   (ND_FLOPPY_DMA command word b7:6 = 0)             **
**   1  floppy drive 1   (b7:6 = 1)                                        **
**   2  Winchester unit 0 (ND_WINCHESTER control word b9 = 0)              **
**   3  Winchester unit 1 (b9 = 1)                                         **
**   4  paper tape       (ND_TAPE_400 byte stream, read from byte 0)       **
** The Winchester card has a ONE-bit unit field, so two units is all it   **
** can address (ND_WINCHESTER.v header). The four-unit device would be the **
** SMD card, which this board does not build.                              **
**                                                                         **
** TWO ADAPTERS PER CONTROLLER. nd_storage_floppy_adapter and              **
** nd_storage_disc_adapter each serve ONE drive/unit (parameter DRIVE /    **
** UNIT) and ignore a request for any other completely - every output      **
** parked at 0 - so two instances share the controller's disk_* inputs and **
** their disk_done/disk_err/err_code/dbuf_* outputs are ORed. The Tang and **
** Nexys aggregator only ever instantiated ONE of each; this is the first  **
** build with a second floppy drive and a second Winchester unit, and the  **
** OR is what nd_storage_mister_devices_tb proves.                         **
**                                                                         **
** OPEN. There is nothing to open on this board - the file is whatever the **
** OSD mounted, and nd_storage_hps reports that as open_ok directly - so   **
** open_start is a single pulse after reset, purely to satisfy the         **
** adapters' contract. A drive whose slot has no image simply answers      **
** every request with err + NDS_ERR_NOTOPEN, which the controllers map     **
** onto their own "not ready" / "disk fault" status bits.                  **
**                                                                         **
** FLOPPY MEDIA FORMAT follows the SELECTED drive: FDISK_MEDIA_FMT is      **
** derived from slot 0's or slot 1's image size according to FDISK_DRIVE,  **
** by the same size rule the Tang aggregator and the C reference use       **
** (315392 bytes = 8-inch 512 B/sector -> 0; else the 1.2 MB 1024 B/sector **
** double-sided double-density descriptor -> 0xF).                         **
**                                                                         **
** Ronny Hansen, 01-SEP-2026                                                **
*****************************************************************************/

module nd_storage_mister_devices #(
    parameter integer BYTE_SWAP = 1     // see nd_storage_hps
) (
    input  wire clk_cpu,  //! 20 MHz, the ND-120 CPU/bus/device domain (from pll_cpu.outclk_0)
    input  wire rst_cpu_n,  //! Active-low reset, from the board's power-on reset (same net as ND120_CORE.sys_rst_n)
    input  wire clk_sys,  //! pixel clock, 40.000 MHz for 800x600@60 (same net as nd120_console_mister.clk)
    input  wire rst_sys_n,  //! async reset, active low, in the pixel domain (same net as nd120_console_mister.rst_n)

    // ---- ND_TAPE_400 byte source port (pin-for-pin) ----
    input  wire       byte_req,  //! pulse: fetch next tape byte (from ND120_CORE.TAPE_BYTE_REQ)
    output wire       byte_valid,  //! pulse: TAPE_BYTE_DATA is the byte (to ND120_CORE.TAPE_BYTE_VALID)
    output wire [7:0] byte_data,
    input  wire       source_rewind,  //! pulse: rewind the tape source (from ND120_CORE.TAPE_REWIND)
    // sticky tape diagnostic - see nd_storage_tape_adapter's header: the
    // ND-400 reader has no register for "no image", so it is published here
    output wire       TDISK_FAULT,
    output wire [3:0] TDISK_ERR_CODE,

    // ---- ND_FLOPPY_DMA disk-image backend seam (slots 0 and 1) ----
    input  wire        FDISK_REQ,
    input  wire        FDISK_WR,
    input  wire [15:0] FDISK_LSECT,
    input  wire [ 1:0] FDISK_FORMAT,
    input  wire [ 1:0] FDISK_DRIVE,
    input  wire [10:0] FDISK_WORDCOUNT,
    output wire        FDISK_DONE,
    output wire        FDISK_ERR,
    output wire [ 3:0] FDISK_ERR_CODE,
    output wire [ 3:0] FDISK_MEDIA_FMT,
    output wire [ 9:0] FDBUF_ADDR,
    output wire [15:0] FDBUF_WDATA,
    output wire        FDBUF_WE,
    input  wire [15:0] FDBUF_RDATA,

    // ---- ND_WINCHESTER disk-image backend seam (slots 2 and 3) ----
    input  wire        WDISK_START,
    input  wire        WDISK_REQ,
    input  wire        WDISK_WR,
    input  wire [15:0] WDISK_BLKADDR1,  //! head b15-8, sector b7-0 (from ND_WINCHESTER.disk_blkaddr1)
    input  wire [15:0] WDISK_BLKADDR2,
    input  wire [ 2:0] WDISK_UNIT,  //! only bit 0 is used - two units (from ND_WINCHESTER.disk_unit)
    input  wire [10:0] WDISK_WORDCOUNT,  //! words in the current chunk (from ND_WINCHESTER.disk_wordcount)
    output wire        WDISK_DONE,
    output wire        WDISK_ERR,
    output wire [ 3:0] WDISK_ERR_CODE,
    output wire [ 9:0] WDBUF_ADDR,
    output wire [15:0] WDBUF_WDATA,
    output wire        WDBUF_WE,
    input  wire [15:0] WDBUF_RDATA,

    // ---- hps_io block interface (clk_sys), flattened per slot ----
    input  wire [4:0]   img_mounted,
    input  wire         img_readonly,
    input  wire [63:0]  img_size,
    output wire [5*32-1:0] sd_lba,
    output wire [5*6-1:0]  sd_blk_cnt,
    output wire [4:0]   sd_rd,
    output wire [4:0]   sd_wr,
    input  wire [4:0]   sd_ack,
    input  wire [12:0]  sd_buff_addr,
    input  wire [15:0]  sd_buff_dout,
    output wire [15:0]  sd_buff_din,
    input  wire         sd_buff_wr,

    // ---- diagnostics (clk_cpu): which slots hold a file ----
    output wire [4:0]   MOUNTED
);

  localparam integer N = 5;
  localparam integer CL_FD0  = 0;
  localparam integer CL_FD1  = 1;
  localparam integer CL_WD0  = 2;
  localparam integer CL_WD1  = 3;
  localparam integer CL_TAPE = 4;

  // ---- flattened client buses ------------------------------------------------
  wire [N-1:0]    open_req_w, open_ok_w, open_err_w, req_w, wr_w;
  wire [N-1:0]    busy_w, done_w, err_w, buf_we_w;
  wire [N*4-1:0]  err_code_w;
  wire [N*32-1:0] size_bytes_w;
  wire [N*16-1:0] block_w, buf_wdata_w, buf_rdata_w;
  wire [N*10-1:0] buf_addr_w;

  // one open pulse after reset, for every adapter at once (nd_storage_hps
  // takes any number of concurrent open_req - there is no card to bring up)
  reg s_open_done;
  always @(posedge clk_cpu or negedge rst_cpu_n)
    if (!rst_cpu_n) s_open_done <= 1'b0;
    else            s_open_done <= 1'b1;
  wire s_open_pulse = !s_open_done;

  // ---- floppy: two adapters, outputs ORed ----------------------------------
  wire        f0_done, f0_err, f0_we, f1_done, f1_err, f1_we;
  wire [3:0]  f0_code, f1_code;
  wire [9:0]  f0_addr, f1_addr;
  wire [15:0] f0_wdata, f1_wdata;

  assign FDISK_DONE     = f0_done  | f1_done;
  assign FDISK_ERR      = f0_err   | f1_err;
  assign FDISK_ERR_CODE = f0_code  | f1_code;
  assign FDBUF_ADDR     = f0_addr  | f1_addr;
  assign FDBUF_WDATA    = f0_wdata | f1_wdata;
  assign FDBUF_WE       = f0_we    | f1_we;

  // media format of the SELECTED drive, from its image size (see header)
  wire [31:0] f0_size = size_bytes_w[CL_FD0*32 +: 32];
  wire [31:0] f1_size = size_bytes_w[CL_FD1*32 +: 32];
  wire [3:0]  f0_fmt  = (f0_size == 32'd315392) ? 4'h0 : 4'hF;
  wire [3:0]  f1_fmt  = (f1_size == 32'd315392) ? 4'h0 : 4'hF;
  assign FDISK_MEDIA_FMT = (FDISK_DRIVE == 2'd1) ? f1_fmt : f0_fmt;

  nd_storage_floppy_adapter #(.DRIVE(2'd0)) u_fd0 (
      .clk_cpu(clk_cpu), .rst_n(rst_cpu_n),
      .disk_req(FDISK_REQ), .disk_wr(FDISK_WR), .disk_lsect(FDISK_LSECT),
      .disk_format(FDISK_FORMAT), .disk_drive(FDISK_DRIVE), .disk_wordcount(FDISK_WORDCOUNT),
      .disk_done(f0_done), .disk_err(f0_err), .disk_err_code(f0_code),
      .dbuf_addr(f0_addr), .dbuf_wdata(f0_wdata), .dbuf_we(f0_we), .dbuf_rdata(FDBUF_RDATA),
      .open_start(s_open_pulse),
      .c_open_req(open_req_w[CL_FD0]), .c_open_ok(open_ok_w[CL_FD0]), .c_open_err(open_err_w[CL_FD0]),
      .c_size_bytes(f0_size),
      .c_req(req_w[CL_FD0]), .c_wr(wr_w[CL_FD0]), .c_block(block_w[CL_FD0*16 +: 16]),
      .c_busy(busy_w[CL_FD0]), .c_done(done_w[CL_FD0]), .c_err(err_w[CL_FD0]),
      .c_err_code(err_code_w[CL_FD0*4 +: 4]),
      .c_buf_addr(buf_addr_w[CL_FD0*10 +: 10]), .c_buf_wdata(buf_wdata_w[CL_FD0*16 +: 16]),
      .c_buf_we(buf_we_w[CL_FD0]), .c_buf_rdata(buf_rdata_w[CL_FD0*16 +: 16])
  );

  nd_storage_floppy_adapter #(.DRIVE(2'd1)) u_fd1 (
      .clk_cpu(clk_cpu), .rst_n(rst_cpu_n),
      .disk_req(FDISK_REQ), .disk_wr(FDISK_WR), .disk_lsect(FDISK_LSECT),
      .disk_format(FDISK_FORMAT), .disk_drive(FDISK_DRIVE), .disk_wordcount(FDISK_WORDCOUNT),
      .disk_done(f1_done), .disk_err(f1_err), .disk_err_code(f1_code),
      .dbuf_addr(f1_addr), .dbuf_wdata(f1_wdata), .dbuf_we(f1_we), .dbuf_rdata(FDBUF_RDATA),
      .open_start(s_open_pulse),
      .c_open_req(open_req_w[CL_FD1]), .c_open_ok(open_ok_w[CL_FD1]), .c_open_err(open_err_w[CL_FD1]),
      .c_size_bytes(f1_size),
      .c_req(req_w[CL_FD1]), .c_wr(wr_w[CL_FD1]), .c_block(block_w[CL_FD1*16 +: 16]),
      .c_busy(busy_w[CL_FD1]), .c_done(done_w[CL_FD1]), .c_err(err_w[CL_FD1]),
      .c_err_code(err_code_w[CL_FD1*4 +: 4]),
      .c_buf_addr(buf_addr_w[CL_FD1*10 +: 10]), .c_buf_wdata(buf_wdata_w[CL_FD1*16 +: 16]),
      .c_buf_we(buf_we_w[CL_FD1]), .c_buf_rdata(buf_rdata_w[CL_FD1*16 +: 16])
  );

  // ---- Winchester: two adapters, outputs ORed --------------------------------
  // The same nd_storage_disc_adapter the Tang uses for the Winchester, with
  // the Micropolis 1325 / DISC-74-1 geometry (8 heads, 9 sectors/track,
  // 1024-byte sectors), one per unit.
  wire        w0_done, w0_err, w0_we, w1_done, w1_err, w1_we;
  wire [3:0]  w0_code, w1_code;
  wire [9:0]  w0_addr, w1_addr;
  wire [15:0] w0_wdata, w1_wdata;

  assign WDISK_DONE     = w0_done  | w1_done;
  assign WDISK_ERR      = w0_err   | w1_err;
  assign WDISK_ERR_CODE = w0_code  | w1_code;
  assign WDBUF_ADDR     = w0_addr  | w1_addr;
  assign WDBUF_WDATA    = w0_wdata | w1_wdata;
  assign WDBUF_WE       = w0_we    | w1_we;

  nd_storage_disc_adapter #(.UNIT(3'd0), .GEO_HEADS(16'd8), .GEO_SPT(16'd9)) u_wd0 (
      .clk_cpu(clk_cpu), .rst_n(rst_cpu_n),
      .disk_start(WDISK_START), .disk_req(WDISK_REQ), .disk_wr(WDISK_WR),
      .disk_blkaddr1(WDISK_BLKADDR1), .disk_blkaddr2(WDISK_BLKADDR2), .disk_unit(WDISK_UNIT),
      .disk_wordcount(WDISK_WORDCOUNT),
      .disk_done(w0_done), .disk_err(w0_err), .disk_err_code(w0_code),
      .dbuf_addr(w0_addr), .dbuf_wdata(w0_wdata), .dbuf_we(w0_we), .dbuf_rdata(WDBUF_RDATA),
      .open_start(s_open_pulse),
      .c_open_req(open_req_w[CL_WD0]), .c_open_ok(open_ok_w[CL_WD0]), .c_open_err(open_err_w[CL_WD0]),
      .c_size_bytes(size_bytes_w[CL_WD0*32 +: 32]),
      .c_req(req_w[CL_WD0]), .c_wr(wr_w[CL_WD0]), .c_block(block_w[CL_WD0*16 +: 16]),
      .c_busy(busy_w[CL_WD0]), .c_done(done_w[CL_WD0]), .c_err(err_w[CL_WD0]),
      .c_err_code(err_code_w[CL_WD0*4 +: 4]),
      .c_buf_addr(buf_addr_w[CL_WD0*10 +: 10]), .c_buf_wdata(buf_wdata_w[CL_WD0*16 +: 16]),
      .c_buf_we(buf_we_w[CL_WD0]), .c_buf_rdata(buf_rdata_w[CL_WD0*16 +: 16])
  );

  nd_storage_disc_adapter #(.UNIT(3'd1), .GEO_HEADS(16'd8), .GEO_SPT(16'd9)) u_wd1 (
      .clk_cpu(clk_cpu), .rst_n(rst_cpu_n),
      .disk_start(WDISK_START), .disk_req(WDISK_REQ), .disk_wr(WDISK_WR),
      .disk_blkaddr1(WDISK_BLKADDR1), .disk_blkaddr2(WDISK_BLKADDR2), .disk_unit(WDISK_UNIT),
      .disk_wordcount(WDISK_WORDCOUNT),
      .disk_done(w1_done), .disk_err(w1_err), .disk_err_code(w1_code),
      .dbuf_addr(w1_addr), .dbuf_wdata(w1_wdata), .dbuf_we(w1_we), .dbuf_rdata(WDBUF_RDATA),
      .open_start(s_open_pulse),
      .c_open_req(open_req_w[CL_WD1]), .c_open_ok(open_ok_w[CL_WD1]), .c_open_err(open_err_w[CL_WD1]),
      .c_size_bytes(size_bytes_w[CL_WD1*32 +: 32]),
      .c_req(req_w[CL_WD1]), .c_wr(wr_w[CL_WD1]), .c_block(block_w[CL_WD1*16 +: 16]),
      .c_busy(busy_w[CL_WD1]), .c_done(done_w[CL_WD1]), .c_err(err_w[CL_WD1]),
      .c_err_code(err_code_w[CL_WD1*4 +: 4]),
      .c_buf_addr(buf_addr_w[CL_WD1*10 +: 10]), .c_buf_wdata(buf_wdata_w[CL_WD1*16 +: 16]),
      .c_buf_we(buf_we_w[CL_WD1]), .c_buf_rdata(buf_rdata_w[CL_WD1*16 +: 16])
  );

  // ---- paper tape -----------------------------------------------------------
  nd_storage_tape_adapter u_tape (
      .clk_cpu(clk_cpu), .rst_n(rst_cpu_n),
      .byte_req(byte_req), .byte_valid(byte_valid), .byte_data(byte_data),
      .source_rewind(source_rewind),
      .fault(TDISK_FAULT), .fault_code(TDISK_ERR_CODE),
      .open_start(s_open_pulse),
      .c_open_req(open_req_w[CL_TAPE]), .c_open_ok(open_ok_w[CL_TAPE]), .c_open_err(open_err_w[CL_TAPE]),
      .c_size_bytes(size_bytes_w[CL_TAPE*32 +: 32]),
      .c_req(req_w[CL_TAPE]), .c_wr(wr_w[CL_TAPE]), .c_block(block_w[CL_TAPE*16 +: 16]),
      .c_busy(busy_w[CL_TAPE]), .c_done(done_w[CL_TAPE]), .c_err(err_w[CL_TAPE]),
      .c_err_code(err_code_w[CL_TAPE*4 +: 4]),
      .c_buf_addr(buf_addr_w[CL_TAPE*10 +: 10]), .c_buf_wdata(buf_wdata_w[CL_TAPE*16 +: 16]),
      .c_buf_we(buf_we_w[CL_TAPE]), .c_buf_rdata(buf_rdata_w[CL_TAPE*16 +: 16])
  );

  // ---- the backend -------------------------------------------------------------
  nd_storage_hps #(.N_CLIENTS(N), .BYTE_SWAP(BYTE_SWAP)) u_hps (
      .clk_cpu(clk_cpu), .rst_cpu_n(rst_cpu_n),
      .open_req(open_req_w), .open_ok(open_ok_w), .open_err(open_err_w), .size_bytes(size_bytes_w),
      .req(req_w), .wr(wr_w), .block(block_w), .busy(busy_w), .done(done_w), .err(err_w),
      .err_code(err_code_w),
      .buf_addr(buf_addr_w), .buf_wdata(buf_wdata_w), .buf_we(buf_we_w), .buf_rdata(buf_rdata_w),
      .clk_sys(clk_sys), .rst_sys_n(rst_sys_n),
      .img_mounted(img_mounted), .img_readonly(img_readonly), .img_size(img_size),
      .sd_lba(sd_lba), .sd_blk_cnt(sd_blk_cnt), .sd_rd(sd_rd), .sd_wr(sd_wr), .sd_ack(sd_ack),
      .sd_buff_addr(sd_buff_addr), .sd_buff_dout(sd_buff_dout), .sd_buff_din(sd_buff_din),
      .sd_buff_wr(sd_buff_wr),
      .mounted(MOUNTED)
  );

endmodule