Skip to content

ND_FLOPPY_PIO

Source: Verilog/ND-BUS-DEVICES/FLOPPY/circuit/ND_FLOPPY_PIO.v

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

Module hierarchy - All modules

ND_FLOPPY_PIO symbol

Schematic

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

Description

ND-100 FLOPPY DISK CONTROLLER, PIO INTERFACE (3027 style) Register core matching the C reference models: Verilog/simDevices/NDDevices.cpp (class FloppyPIO) nd100x src/devices/floppy/deviceFloppyPIO.c Register spec: ND-11.012.01 / ND-11.015.01 IOX 1560+0 R read data buffer (word, auto-increment pointer) +1 W write data buffer (word, auto-increment pointer) +2 R read status register 1 +3 W write control word +4 R read status register 2 +5 W write drive address (b0=1) / write difference (b0=0) +6 R read test data (test mode) +7 W write sector (b0 n/a) / write test byte (test mode) Ident code 021 (octal), interrupt level 11. Commands (control word b8-15, highest set bit wins): b8 format track, b9 write data, b10 write deleted data, b11 read ID, b12 read data, b13 seek, b14 recalibrate, b15 control reset Completion after the disk backend finishes + DELAY_TICKS; raises level-11 interrupt if enabled. IDENT clears pending + int enable. The 1024-word interface buffer lives here (BRAM). The disk image is behind the disk_* backend port: the sim harness serves an image file, hardware serves the SDRAM-cached image with SD write-through. NOT ported from the C models (ask before adding - documented hacks): - RSR1 read "magic" bits 9/10/11 derived from the buffer pointer (exists only to satisfy a diagnostic test program) - autoload's hardcoded 388-byte boot blob (real hardware reads track 0 sector 1; FLO-MON must be on the diskette) - deleted-sector shadow array (no room in the image format) Last reviewed: 11-JUL-2026 Ronny Hansen

Parameters

Parameter Default
BASE_ADDR 16'o001560
IDENT_CODE 16'o000021
INT_LEVEL 4'd11
DELAY_TICKS 16'd3000 // C model: IODELAY_FLOPPY

Ports

Direction Width Name Description
input 1 sysclk
input 1 sys_rst_n (active low)
input [15:0] iox_addr
input 1 iox_wr
input [15:0] iox_wdata
input 1 iox_rd
output [15:0] iox_rdata
output [3:0] int_pending
input 1 ident_strobe
input [3:0] ident_level
input 1 ident_grant_in
output 1 ident_grant_out
output 1 ident_hit
output [15:0] ident_code
output 1 disk_req pulse: start operation
output [2:0] disk_op command enum (0-7, see above)
output [6:0] disk_sector 1-based
output [6:0] disk_track 0-76
output [1:0] disk_format 0/1: 128Bx26 2: 256Bx15 3: 512Bx8
output [2:0] disk_drive selected unit
output [9:0] disk_buf_start buffer pointer at command time
output [10:0] disk_wordcount words to transfer
input 1 disk_done pulse: operation finished
input 1 disk_err_notrdy with done: drive not ready
input 1 disk_err_missing with done: sector missing
input [9:0] dbuf_addr
input [15:0] dbuf_wdata
input 1 dbuf_we
output [15:0] dbuf_rdata

Verilog source

Verilog/ND-BUS-DEVICES/FLOPPY/circuit/ND_FLOPPY_PIO.v on GitHub.

Show the Verilog of ND_FLOPPY_PIO (412 lines)
/**************************************************************************
** ND-100 FLOPPY DISK CONTROLLER, PIO INTERFACE (3027 style)             **
**                                                                       **
** Register core matching the C reference models:                        **
**   Verilog/simDevices/NDDevices.cpp (class FloppyPIO)                  **
**   nd100x src/devices/floppy/deviceFloppyPIO.c                         **
** Register spec: ND-11.012.01 / ND-11.015.01                            **
**                                                                       **
** IOX 1560+0 R  read data buffer (word, auto-increment pointer)         **
**         +1 W  write data buffer (word, auto-increment pointer)        **
**         +2 R  read status register 1                                  **
**         +3 W  write control word                                      **
**         +4 R  read status register 2                                  **
**         +5 W  write drive address (b0=1) / write difference (b0=0)    **
**         +6 R  read test data (test mode)                              **
**         +7 W  write sector (b0 n/a) / write test byte (test mode)     **
**                                                                       **
** Ident code 021 (octal), interrupt level 11.                           **
** Commands (control word b8-15, highest set bit wins):                  **
**   b8 format track, b9 write data, b10 write deleted data, b11 read    **
**   ID, b12 read data, b13 seek, b14 recalibrate, b15 control reset     **
** Completion after the disk backend finishes + DELAY_TICKS; raises      **
** level-11 interrupt if enabled. IDENT clears pending + int enable.     **
**                                                                       **
** The 1024-word interface buffer lives here (BRAM). The disk image is   **
** behind the disk_* backend port: the sim harness serves an image       **
** file, hardware serves the SDRAM-cached image with SD write-through.   **
**                                                                       **
** NOT ported from the C models (ask before adding - documented hacks):  **
**   - RSR1 read "magic" bits 9/10/11 derived from the buffer pointer    **
**     (exists only to satisfy a diagnostic test program)                **
**   - autoload's hardcoded 388-byte boot blob (real hardware reads      **
**     track 0 sector 1; FLO-MON must be on the diskette)                **
**   - deleted-sector shadow array (no room in the image format)        **
**                                                                       **
** Last reviewed: 11-JUL-2026                                            **
** Ronny Hansen                                                          **
***************************************************************************/

module ND_FLOPPY_PIO #(
    parameter [15:0] BASE_ADDR   = 16'o001560,
    parameter [15:0] IDENT_CODE  = 16'o000021,
    parameter [3:0]  INT_LEVEL   = 4'd11,
    parameter [15:0] DELAY_TICKS = 16'd3000  // C model: IODELAY_FLOPPY
) (
    input wire sysclk,
    input wire sys_rst_n,

    // Device bus (from ND_BUS_SLAVE)
    input  wire [15:0] iox_addr,
    input  wire        iox_wr,
    input  wire [15:0] iox_wdata,
    input  wire        iox_rd,
    output reg  [15:0] iox_rdata,
    output wire [3:0]  int_pending,
    input  wire        ident_strobe,
    input  wire [3:0]  ident_level,
    input  wire        ident_grant_in,
    output wire        ident_grant_out,
    output wire        ident_hit,
    output wire [15:0] ident_code,

    // Disk image backend (sim: file image; hardware: SDRAM cache + SD)
    output reg         disk_req,       // pulse: start operation
    output reg  [2:0]  disk_op,        // command enum (0-7, see above)
    output wire [6:0]  disk_sector,    // 1-based
    output wire [6:0]  disk_track,     // 0-76
    output wire [1:0]  disk_format,    // 0/1: 128Bx26  2: 256Bx15  3: 512Bx8
    output wire [2:0]  disk_drive,     // selected unit
    output wire [9:0]  disk_buf_start, // buffer pointer at command time
    output wire [10:0] disk_wordcount, // words to transfer
    input  wire        disk_done,      // pulse: operation finished
    input  wire        disk_err_notrdy,   // with done: drive not ready
    input  wire        disk_err_missing,  // with done: sector missing

    // Backend port into the interface buffer
    input  wire [9:0]  dbuf_addr,
    input  wire [15:0] dbuf_wdata,
    input  wire        dbuf_we,
    output reg  [15:0] dbuf_rdata
);

  // Commands (control-word bit - 8)
  localparam CMD_FORMAT    = 3'd0;
  localparam CMD_WRITE     = 3'd1;
  localparam CMD_WRITEDEL  = 3'd2;
  localparam CMD_READID    = 3'd3;
  localparam CMD_READ      = 3'd4;
  localparam CMD_SEEK      = 3'd5;
  localparam CMD_RECAL     = 3'd6;
  localparam CMD_CTLRESET  = 3'd7;

  // Interface buffer, 1024 x 16 (BRAM)
  reg [15:0] s_buffer[0:1023];
  reg [9:0]  s_bufptr;

  // Status register 1 bits
  reg s_int_enabled;   // b1
  reg s_busy;          // b2
  reg s_rft;           // b3 device ready for transfer
  reg s_deleted;       // b5
  reg s_rw_complete;   // b6
  reg s_seek_complete; // b7
  reg s_timeout;       // b8

  // Status register 2 bits
  reg s_not_ready;     // b8
  reg s_write_prot;    // b9
  reg s_missing;       // b11
  reg s_crc_err;       // b12
  reg s_overrun;       // b14

  wire [15:0] s_rsr2 = {1'b0, s_overrun, 1'b0, s_crc_err, s_missing, 1'b0,
                        s_write_prot, s_not_ready, 8'd0};
  wire s_rsr2_or = (s_rsr2 != 16'd0);
  wire [15:0] s_rsr1 = {7'd0, s_timeout, s_seek_complete, s_rw_complete,
                        s_deleted, s_rsr2_or, s_rft, s_busy, s_int_enabled, 1'b0};

  // Geometry / addressing
  reg [1:0] s_format;        // formatSelect
  reg [2:0] s_drive;         // selected unit
  reg       s_drive_sel;     // a drive is selected
  reg [6:0] s_track;
  reg [6:0] s_sector;        // 1-based
  reg       s_auto_incr;
  reg       s_test_mode;
  reg [7:0] s_test_byte;
  reg       s_testmode_hi;   // test-data byte phase

  wire [4:0] s_sect_per_track = (s_format == 2'd2) ? 5'd15 :
                                (s_format == 2'd3) ? 5'd8  : 5'd26;
  wire [10:0] s_words_per_sector = (s_format == 2'd2) ? 11'd128 :
                                   (s_format == 2'd3) ? 11'd256 : 11'd64;

  // Command engine
  reg [2:0]  s_cmd;
  reg        s_cmd_running;   // waiting for disk_done
  reg        s_delay_running; // completion delay after disk_done
  reg [15:0] s_delay_cnt;
  reg [9:0]  s_cmd_buf_start;

  assign disk_sector    = s_sector;
  assign disk_track     = s_track;
  assign disk_format    = s_format;
  assign disk_drive     = s_drive;
  assign disk_buf_start = s_cmd_buf_start;
  assign disk_wordcount = s_words_per_sector;

  // Address decode
  wire s_addressed = (iox_addr[15:3] == BASE_ADDR[15:3]);
  wire [2:0] s_reg = iox_addr[2:0];

  // Interrupt: enabled AND ready (evaluated continuously)
  wire s_pending = s_int_enabled && s_rft;
  assign int_pending = {(INT_LEVEL == 4'd13) && s_pending,
                        (INT_LEVEL == 4'd12) && s_pending,
                        (INT_LEVEL == 4'd11) && s_pending,
                        (INT_LEVEL == 4'd10) && s_pending};

  wire s_ident_answer = ident_strobe && ident_grant_in &&
                        (ident_level == INT_LEVEL) && s_pending;
  assign ident_hit       = s_ident_answer;
  assign ident_code      = s_ident_answer ? IDENT_CODE : 16'd0;
  assign ident_grant_out = ident_grant_in && !s_ident_answer;

  // Read mux (OR-bus: 0 when not addressed)
  always @(*) begin
    iox_rdata = 16'd0;
    if (iox_rd && s_addressed) begin
      case (s_reg)
        3'd0: iox_rdata = s_buffer[s_bufptr];
        3'd2: iox_rdata = s_rsr1;
        3'd4: iox_rdata = s_rsr2;
        3'd6: iox_rdata = 16'd0;  // test data handled in the clocked block
        default: iox_rdata = 16'd0;
      endcase
    end
  end

  // Backend buffer port (read is combinational like the CPU-side mux)
  always @(*) begin
    dbuf_rdata = s_buffer[dbuf_addr];
  end

  // Command decode: highest set bit of the control-word high byte wins
  function [2:0] cmd_decode(input [7:0] hi);
    integer k;
    begin
      cmd_decode = 3'd0;
      for (k = 0; k < 8; k = k + 1) begin
        if (hi[k]) cmd_decode = k[2:0];
      end
    end
  endfunction

  wire s_wr_here = iox_wr && s_addressed;
  wire [2:0] s_cmd_dec = cmd_decode(iox_wdata[15:8]);

  always @(posedge sysclk or negedge sys_rst_n) begin
    if (!sys_rst_n) begin
      s_bufptr        <= 10'd0;
      s_int_enabled   <= 1'b0;
      s_busy          <= 1'b0;
      s_rft           <= 1'b1;  // C model Reset(): ready for transfer
      s_deleted       <= 1'b0;
      s_rw_complete   <= 1'b0;
      s_seek_complete <= 1'b0;
      s_timeout       <= 1'b0;
      s_not_ready     <= 1'b0;
      s_write_prot    <= 1'b0;
      s_missing       <= 1'b0;
      s_crc_err       <= 1'b0;
      s_overrun       <= 1'b0;
      s_format        <= 2'd0;
      s_drive         <= 3'd0;
      s_drive_sel     <= 1'b0;
      s_track         <= 7'd0;
      s_sector        <= 7'd1;
      s_auto_incr     <= 1'b0;
      s_test_mode     <= 1'b0;
      s_test_byte     <= 8'd0;
      s_testmode_hi   <= 1'b0;
      s_cmd           <= 3'd0;
      s_cmd_running   <= 1'b0;
      s_delay_running <= 1'b0;
      s_delay_cnt     <= 16'd0;
      s_cmd_buf_start <= 10'd0;
      disk_req        <= 1'b0;
      disk_op         <= 3'd0;
    end else begin
      disk_req <= 1'b0;

      // Backend writes into the interface buffer (sector read fill)
      if (dbuf_we) begin
        s_buffer[dbuf_addr] <= dbuf_wdata;
      end

      // CPU data-buffer access: auto-increment pointer
      if (iox_rd && s_addressed && (s_reg == 3'd0)) begin
        s_bufptr <= s_bufptr + 10'd1;
      end
      if (s_wr_here && (s_reg == 3'd1)) begin
        s_buffer[s_bufptr] <= iox_wdata;
        s_bufptr <= s_bufptr + 10'd1;
      end

      // Control word
      if (s_wr_here && (s_reg == 3'd3)) begin
        s_int_enabled <= iox_wdata[1];
        s_test_mode   <= iox_wdata[3];

        if (iox_wdata[4]) begin  // device clear
          s_drive_sel <= 1'b0;
          s_bufptr    <= 10'd0;
          s_rft       <= 1'b1;
          s_not_ready <= 1'b0;
          s_write_prot<= 1'b0;
          s_missing   <= 1'b0;
          s_crc_err   <= 1'b0;
          s_overrun   <= 1'b0;
        end
        if (iox_wdata[5]) begin  // clear interface buffer address
          s_bufptr <= 10'd0;
          s_rft    <= 1'b1;
        end

        if (iox_wdata[15:8] != 8'd0) begin
          // A command: clear errors and completion flags, go busy
          s_busy          <= 1'b1;
          s_rft           <= 1'b0;
          s_rw_complete   <= 1'b0;
          s_seek_complete <= 1'b0;
          s_deleted       <= 1'b0;
          s_not_ready     <= 1'b0;
          s_write_prot    <= 1'b0;
          s_missing       <= 1'b0;
          s_crc_err       <= 1'b0;
          s_overrun       <= 1'b0;
          s_cmd           <= s_cmd_dec;
          s_cmd_buf_start <= s_bufptr;

          case (s_cmd_dec)
            CMD_CTLRESET: begin
              // No interrupt per documentation; just not busy
              s_busy <= 1'b0;
            end
            CMD_RECAL: begin
              s_track  <= 7'd0;
              s_sector <= 7'd1;
              s_delay_running <= 1'b1;
              s_delay_cnt     <= DELAY_TICKS;
            end
            CMD_SEEK: begin
              s_delay_running <= 1'b1;
              s_delay_cnt     <= DELAY_TICKS;
            end
            CMD_READID: begin
              // ID field: track / zero, sector / zero (see C model note:
              // byte 3 hex 02 removed - FLOPPY-FU-1986F complains)
              s_buffer[0] <= {1'b0, s_track, 8'd0};
              s_buffer[1] <= {1'b0, s_sector, 8'd0};
              s_bufptr    <= 10'd0;
              s_cmd_buf_start <= 10'd0;
              s_delay_running <= 1'b1;
              s_delay_cnt     <= DELAY_TICKS;
            end
            default: begin
              // READ / WRITE / WRITEDEL / FORMAT go to the disk backend
              if ({2'b0, s_sector} > {2'b0, s_sect_per_track}) begin
                s_missing <= 1'b1;
                s_rft     <= 1'b1;
                s_busy    <= 1'b0;
              end else if (!s_drive_sel) begin
                s_not_ready <= 1'b1;
                s_busy      <= 1'b0;
              end else begin
                disk_req <= 1'b1;
                disk_op  <= s_cmd_dec;
                s_cmd_running <= 1'b1;
              end
            end
          endcase
        end
      end

      // Drive address / difference
      if (s_wr_here && (s_reg == 3'd5)) begin
        if (iox_wdata[0]) begin
          // Write drive address
          s_drive     <= iox_wdata[10:8];
          s_drive_sel <= !iox_wdata[11];
          s_format    <= iox_wdata[15:14];
        end else begin
          // Write difference: move head, clamp track to 0..76
          if (iox_wdata[15]) begin
            s_track <= (({1'b0, s_track} + {1'b0, iox_wdata[14:8]}) > 8'd76)
                       ? 7'd76 : (s_track + iox_wdata[14:8]);
          end else begin
            s_track <= (s_track < iox_wdata[14:8]) ? 7'd0
                       : (s_track - iox_wdata[14:8]);
          end
        end
      end

      // Sector / test byte
      if (s_wr_here && (s_reg == 3'd7)) begin
        if (s_test_mode) begin
          s_test_byte <= iox_wdata[15:8];
        end else begin
          s_sector    <= iox_wdata[14:8];
          s_auto_incr <= iox_wdata[15];
        end
      end

      // Test-data read: shift the test byte into the buffer (C model)
      if (iox_rd && s_addressed && (s_reg == 3'd6) && s_test_mode) begin
        if (s_testmode_hi) begin
          s_buffer[s_bufptr] <= {s_buffer[s_bufptr][15:8], s_test_byte};
          s_bufptr      <= s_bufptr + 10'd1;
          s_testmode_hi <= 1'b0;
        end else begin
          s_buffer[s_bufptr] <= {s_test_byte, s_buffer[s_bufptr][7:0]};
          s_testmode_hi <= 1'b1;
        end
      end

      // Disk backend finished: start the completion delay
      if (s_cmd_running && disk_done) begin
        s_cmd_running <= 1'b0;
        if (disk_err_notrdy) begin
          s_not_ready <= 1'b1;
          s_busy      <= 1'b0;
        end else if (disk_err_missing) begin
          s_missing <= 1'b1;
          s_busy    <= 1'b0;
        end else begin
          // Data commands advance the buffer pointer by one sector
          if (s_cmd != CMD_FORMAT) begin
            s_bufptr <= s_cmd_buf_start + s_words_per_sector[9:0];
          end
          s_delay_running <= 1'b1;
          s_delay_cnt     <= DELAY_TICKS;
        end
      end

      // Completion delay -> completion flags + interrupt condition
      if (s_delay_running) begin
        if (s_delay_cnt != 16'd0) begin
          s_delay_cnt <= s_delay_cnt - 16'd1;
        end else begin
          s_delay_running <= 1'b0;
          s_busy          <= 1'b0;
          s_rft           <= 1'b1;
          if (s_cmd == CMD_SEEK || s_cmd == CMD_RECAL) begin
            s_seek_complete <= 1'b1;
          end else begin
            s_rw_complete <= 1'b1;
            if (s_auto_incr && (s_sector <= {2'b0, s_sect_per_track})) begin
              s_sector <= s_sector + 7'd1;
            end
          end
        end
      end

      // IDENT answered: clear the enable bit (pending follows)
      if (s_ident_answer) begin
        s_int_enabled <= 1'b0;
      end
    end
  end

endmodule