Skip to content

nd_storage_mega65_devices

Source: Verilog/fpga/mega65/rtl/nd_storage_mega65_devices.v

Where it sits (MEGA65 R6): nd120_mega65_machine > nd_storage_mega65_devices - instance path: STORAGE

Used in: nd120_mega65_machine (MEGA65 R6, MEGA65 R3)

Contains: nd_storage_disc_adapter x2, nd_storage_floppy_adapter x2, nd_storage_tape_adapter, nd_storage_vdrives

Module hierarchy - All modules

nd_storage_mega65_devices symbol

Schematic

Drawn from the Verilog: the yosys netlist of the MEGA65 R4 R5 R6 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_mega65_devices schematic

Parameters

Parameter Default
N_CLIENTS 5 // fixed by the slot map; kept as a parameter for the machine top

Ports

Direction Width Name Description
input 1 clk_cpu 20 MHz (from nd120_mega65_machine.clk_cpu)
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
input 1 rst_sys_n (active low)
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 [31: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 [13:0] sd_buff_addr
input [7:0] sd_buff_dout
output [7:0] sd_buff_din
input 1 sd_buff_wr
output [4:0] MOUNTED

Verilog source

Verilog/fpga/mega65/rtl/nd_storage_mega65_devices.v on GitHub.

Show the Verilog of nd_storage_mega65_devices (252 lines)
`include "nd_storage_status.vh"
/****************************************************************************
** nd_storage_mega65_devices - the ND-120 storage subsystem on the MEGA65  **
**                                                                         **
** Full path: Verilog/fpga/mega65/rtl/nd_storage_mega65_devices.v          **
**                                                                         **
** Copied 02-SEP-2026 from fpga/mister/rtl/nd_storage_mister_devices.v -   **
** the "storage subsystem in a box" with pin-for-pin FDISK_* and FDBUF_*,  **
** WDISK_* and WDBUF_* and TAPE byte-source seams matching ND120_CORE, two **
** floppy drives, two Winchester units and the paper tape on the same      **
** three controller-facing adapters. Read that file's header for the slot  **
** map, the two-adapters-per-controller OR, the open semantics and the     **
** floppy media-format rule; all of it is unchanged. The ONLY difference:   **
** the backend is nd_storage_vdrives (MiSTer2MEGA65's virtual drives,      **
** byte-wide buffer bus in the QNICE clock domain) instead of              **
** nd_storage_vdrives, so the sd_buff_* ports here are 8 bits wide with a      **
** 14-bit byte address, and img_size is 32 bits.                            **
**                                                                         **
** SLOT MAP (= the mount lines in CORE/vhdl/config.vhd, in order, =        **
** vdrives VDNUM index, = nd_storage_vdrives client):                       **
**   0  floppy drive 0    1  floppy drive 1                                 **
**   2  Winchester unit 0 3  Winchester unit 1                              **
**   4  paper tape                                                          **
**                                                                         **
** Ronny Hansen, 02-SEP-2026                                                **
*****************************************************************************/

module nd_storage_mega65_devices #(
    parameter integer N_CLIENTS = 5     // fixed by the slot map; kept as a parameter for the machine top
) (
    input  wire clk_cpu,  //! 20 MHz (from nd120_mega65_machine.clk_cpu)
    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,
    input  wire rst_sys_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,

    // ---- vdrives block interface (clk_sys = QNICE clock), flattened per slot ----
    input  wire [4:0]   img_mounted,
    input  wire         img_readonly,
    input  wire [31: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 [13:0]  sd_buff_addr,
    input  wire [7:0]   sd_buff_dout,
    output wire [7: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_vdrives
  // 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_vdrives #(.N_CLIENTS(N)) u_vd (
      .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