Skip to content

nd_storage_vdrives

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

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

Used in: nd_storage_mega65_devices (MEGA65 R6, MEGA65 R3)

Contains: no other modules.

Module hierarchy - All modules

nd_storage_vdrives symbol

Schematic

Drawn from the Verilog: the yosys netlist of the MEGA65 R4 R5 R6 build, instance STORAGE.u_vd. 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_vdrives schematic

Parameters

Parameter Default
N_CLIENTS 5 // = vdrives VDNUM; slot n = client n

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 [N_CLIENTS-1:0] open_req
output [N_CLIENTS-1:0] open_ok
output [N_CLIENTS-1:0] open_err
output [N_CLIENTS*32-1:0] size_bytes
input [N_CLIENTS-1:0] req
input [N_CLIENTS-1:0] wr
input [N_CLIENTS*16-1:0] block
output [N_CLIENTS-1:0] busy
output [N_CLIENTS-1:0] done
output [N_CLIENTS-1:0] err
output [N_CLIENTS*4-1:0] err_code
output [N_CLIENTS*10-1:0] buf_addr
output [N_CLIENTS*16-1:0] buf_wdata
output [N_CLIENTS-1:0] buf_we
input [N_CLIENTS*16-1:0] buf_rdata
input 1 clk_sys
input 1 rst_sys_n (active low)
input [N_CLIENTS-1:0] img_mounted
input 1 img_readonly
input [31:0] img_size
output [N_CLIENTS*32-1:0] sd_lba
output [N_CLIENTS*6-1:0] sd_blk_cnt
output [N_CLIENTS-1:0] sd_rd
output [N_CLIENTS-1:0] sd_wr
input [N_CLIENTS-1:0] sd_ack
input [13:0] sd_buff_addr BYTE address within the transaction
input [7:0] sd_buff_dout
output [7:0] sd_buff_din
input 1 sd_buff_wr
output [N_CLIENTS-1:0] mounted

Verilog source

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

Show the Verilog of nd_storage_vdrives (508 lines)
`include "nd_storage_status.vh"

/****************************************************************************
** nd_storage_vdrives - the ND-120 storage client bus served by            **
**                      MiSTer2MEGA65 virtual drives                       **
**                                                                         **
** Full path: Verilog/fpga/mega65/rtl/nd_storage_vdrives.v                 **
**                                                                         **
** The MEGA65 counterpart of fpga/mister/rtl/nd_storage_hps.v, from which  **
** it is copied (02-SEP-2026, from the version that mounted floppy0/1, WD0  **
** and tape on the DE10-Nano). Same client port (Verilog/docs/              **
** nd-storage-interface-spec.md section 4: one request = one 2048-byte      **
** block = 1024 ND words, backend masters the client's buffer), same        **
** request/mount/error rules, same two-domain design with the staging      **
** buffers and the 4-phase go/done handshake. Read that file's header for   **
** the rules; only what DIFFERS is described here.                          **
**                                                                         **
** THE OTHER SIDE is the framework's vdrives.vhd (m2m/M2M/vhdl/), which     **
** implements the MiSTer sd_* block protocol in the QNICE clock domain      **
** (50 MHz) with the QNICE soft CPU's firmware doing what the firmware does on   **
** the MiSTer: it polls sd_rd/sd_wr, raises sd_ack[slot] for the whole      **
** transaction, moves the data through sd_buff_*, drops ack. Differences   **
** from hps_io that this file absorbs:                                      **
**   - the buffer bus is BYTE wide: sd_buff_dout/din are 8 bits and        **
**     sd_buff_addr (14 bits) counts BYTES 0..2047 of the 2048-byte         **
**     transaction. The bytes arrive in file order, so ND word w =          **
**     {byte 2w, byte 2w+1} needs no byte swap at all - the staging         **
**     buffers are kept as two byte planes (high = even byte).              **
**   - sd_buff_wr is a REGISTER the firmware writes 1 then 0, so it can be  **
**     high for several clocks; the address and data are set before it and **
**     hold, so writing the byte on every cycle it is high is harmless.     **
**   - for writes (core -> file) the firmware sets sd_buff_addr and reads   **
**     sd_buff_din through MMIO instructions later: a registered read path  **
**     of two clocks is far inside that.                                     **
**   - img_size is 32 bits (hps_io: 64).                                    **
**   - sd_blk_cnt = 3 with BLKSZ 2 (512-byte blocks): one 2048-byte          **
**     transaction, as on the MiSTer; vdrives.vhd documents that the        **
**     firmware honours it ("BLK_CNT blocks long").                          **
** Slot n = vdrive n = the n-th "%s" mount line in config.vhd's OPTM_ITEMS. **
**                                                                         **
** Ronny Hansen, 02-SEP-2026                                                **
*****************************************************************************/

module nd_storage_vdrives #(
    parameter integer N_CLIENTS = 5    // = vdrives VDNUM; slot n = client n
) (
    // ---- client ports (clk_cpu domain, flattened; spec section 4) ----
    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 [N_CLIENTS-1:0]    open_req,
    output wire [N_CLIENTS-1:0]    open_ok,
    output wire [N_CLIENTS-1:0]    open_err,
    output wire [N_CLIENTS*32-1:0] size_bytes,
    input  wire [N_CLIENTS-1:0]    req,
    input  wire [N_CLIENTS-1:0]    wr,
    input  wire [N_CLIENTS*16-1:0] block,
    output wire [N_CLIENTS-1:0]    busy,
    output reg  [N_CLIENTS-1:0]    done,
    output reg  [N_CLIENTS-1:0]    err,
    output wire [N_CLIENTS*4-1:0]  err_code,
    output wire [N_CLIENTS*10-1:0] buf_addr,
    output wire [N_CLIENTS*16-1:0] buf_wdata,
    output wire [N_CLIENTS-1:0]    buf_we,
    input  wire [N_CLIENTS*16-1:0] buf_rdata,

    // ---- vdrives block interface (clk_sys = the QNICE clock) ----
    input  wire                    clk_sys,
    input  wire                    rst_sys_n,
    input  wire [N_CLIENTS-1:0]    img_mounted,
    input  wire                    img_readonly,
    input  wire [31:0]             img_size,
    output wire [N_CLIENTS*32-1:0] sd_lba,
    output wire [N_CLIENTS*6-1:0]  sd_blk_cnt,
    output reg  [N_CLIENTS-1:0]    sd_rd,
    output reg  [N_CLIENTS-1:0]    sd_wr,
    input  wire [N_CLIENTS-1:0]    sd_ack,
    input  wire [13:0]             sd_buff_addr,   // BYTE address within the transaction
    input  wire [7:0]              sd_buff_dout,
    output wire [7:0]              sd_buff_din,
    input  wire                    sd_buff_wr,

    // ---- diagnostics (clk_cpu domain): which slots hold a file ----
    output wire [N_CLIENTS-1:0]    mounted
);

  localparam integer N = N_CLIENTS;

  // one client block = 4 HPS blocks of 512 bytes, sent as ONE transaction
  localparam [5:0] BLK_CNT_M1 = 6'd3;

  // =======================================================================
  // clk_sys side: mount records + the QNICE firmware transaction
  // =======================================================================

  // ---- mount records, latched on the rising edge of img_mounted[n] ------
  // vdrives strobes img_mounted for a few clocks (its firmware writes the
  // register 1 then 0), so it is edge-detected. img_size/img_readonly are
  // set before the strobe (vdrives.vhd rule 2) and are stable here.
  reg [N-1:0]  m_mounted;
  reg [N-1:0]  m_ro;
  reg [N*32-1:0] m_size;
  reg [N-1:0]  m_tgl;          // flips once per mount/unmount event
  reg [N-1:0]  img_mounted_d;

  integer n;
  always @(posedge clk_sys or negedge rst_sys_n) begin
    if (!rst_sys_n) begin
      m_mounted     <= {N{1'b0}};
      m_ro          <= {N{1'b0}};
      m_size        <= {(N*32){1'b0}};
      m_tgl         <= {N{1'b0}};
      img_mounted_d <= {N{1'b0}};
    end else begin
      img_mounted_d <= img_mounted;
      for (n = 0; n < N; n = n + 1) begin
        if (img_mounted[n] && !img_mounted_d[n]) begin
          m_mounted[n]         <= |img_size;          // size 0 = unmount
          m_ro[n]              <= img_readonly;
          m_size[n*32 +: 32]   <= img_size;
          m_tgl[n]             <= ~m_tgl[n];
        end
      end
    end
  end

  // ---- request fields, written by the clk_cpu side, stable while t_go ---
  reg        t_go;             // clk_cpu: request handed to the QNICE side
  reg        t_wr;
  reg [31:0] t_lba;
  reg [N-1:0] t_sel;           // one-hot slot

  // go -> clk_sys, done -> clk_cpu, 4-phase
  reg [1:0] go_s;              // clk_sys copy of t_go
  reg       h_done;            // clk_sys: transaction finished
  reg [1:0] hd_s;              // clk_cpu copy of h_done

  // vdrives wants a per-slot array; every slot carries the same request,
  // only the selected slot's sd_rd/sd_wr bit is raised.
  genvar gs;
  generate
    for (gs = 0; gs < N; gs = gs + 1) begin : g_lba
      assign sd_lba[gs*32 +: 32]   = t_lba;
      assign sd_blk_cnt[gs*6 +: 6] = BLK_CNT_M1;
    end
  endgenerate

  // ---- the two staging buffers ------------------------------------------
  // Byte planes: the QNICE side moves one byte per strobe, the client side
  // one 16-bit word per cycle. ND words are big-endian pairs of the file's
  // bytes, so byte 2w is the HIGH half of word w.
  reg [7:0]  rbuf_hi[0:1023];  // QNICE -> client, even bytes: written on clk_sys
  reg [7:0]  rbuf_lo[0:1023];  //                  odd bytes
  reg [15:0] wbuf[0:1023];     // client -> QNICE: written on clk_cpu
  reg [15:0] wbuf_q;           // clk_sys registered read of wbuf
  reg        wsel_q;           // which half the firmware asked for
  reg [15:0] rbuf_q;           // clk_cpu registered read of rbuf

  wire ack_sel = |(sd_ack & t_sel);

  // read direction: every byte the firmware strobes lands in its plane.
  // sd_buff_wr may stay high for several clocks (it is a firmware-written
  // register); address and data hold meanwhile, so re-writing is harmless.
  // Only the low 11 address bits matter (2048 bytes).
  always @(posedge clk_sys) begin
    if (ack_sel && sd_buff_wr && !t_wr) begin
      if (!sd_buff_addr[0]) rbuf_hi[sd_buff_addr[10:1]] <= sd_buff_dout;
      else                  rbuf_lo[sd_buff_addr[10:1]] <= sd_buff_dout;
    end
    wbuf_q <= wbuf[sd_buff_addr[10:1]];
    wsel_q <= sd_buff_addr[0];
  end
  // write direction: the firmware sets the address, then reads the byte
  // through an MMIO instruction some clocks later; the two-clock registered
  // path above is ready long before that.
  assign sd_buff_din = wsel_q ? wbuf_q[7:0] : wbuf_q[15:8];

  // ---- QNICE transaction FSM (identical to the QNICE firmware one) -----------------
  localparam [1:0] H_IDLE = 2'd0,   // wait for go
                   H_REQ  = 2'd1,   // sd_rd/sd_wr raised, wait ack rising
                   H_XFER = 2'd2,   // ack high: the stream runs
                   H_DONE = 2'd3;   // tell the client side; wait go low

  reg [1:0] h_state;
  reg       ack_sel_d;

  always @(posedge clk_sys or negedge rst_sys_n) begin
    if (!rst_sys_n) begin
      go_s      <= 2'b00;
      h_state   <= H_IDLE;
      h_done    <= 1'b0;
      sd_rd     <= {N{1'b0}};
      sd_wr     <= {N{1'b0}};
      ack_sel_d <= 1'b0;
    end else begin
      go_s      <= {go_s[0], t_go};
      ack_sel_d <= ack_sel;

      case (h_state)
        H_IDLE: begin
          if (go_s[1]) begin
            // t_wr / t_lba / t_sel have been stable since before t_go rose
            if (t_wr) sd_wr <= t_sel;
            else      sd_rd <= t_sel;
            h_state <= H_REQ;
          end
        end

        H_REQ: begin
          // the request is held until the firmware has picked it up (ack rises),
          // then dropped so the next poll does not run it a second time
          if (ack_sel) begin
            sd_rd   <= {N{1'b0}};
            sd_wr   <= {N{1'b0}};
            h_state <= H_XFER;
          end
        end

        H_XFER: begin
          // the data moves in the always block above; the falling edge of
          // ack is the end of the transaction
          if (!ack_sel && ack_sel_d) begin
            h_done  <= 1'b1;
            h_state <= H_DONE;
          end
        end

        H_DONE: begin
          if (!go_s[1]) begin
            h_done  <= 1'b0;
            h_state <= H_IDLE;
          end
        end
      endcase
    end
  end

  // =======================================================================
  // clk_cpu side: mount copy, arbiter, client streaming
  // =======================================================================

  // ---- mount records copied over on the toggle ---------------------------
  reg [N-1:0]   tgl_s1, tgl_s2, tgl_seen;
  reg [N-1:0]   c_mounted;
  reg [N-1:0]   c_ro;
  reg [N*32-1:0] c_size;
  reg [N-1:0]   c_open_seen;    // an open_req was issued (for open_err)

  always @(posedge clk_cpu or negedge rst_cpu_n) begin
    if (!rst_cpu_n) begin
      tgl_s1      <= {N{1'b0}};
      tgl_s2      <= {N{1'b0}};
      tgl_seen    <= {N{1'b0}};
      c_mounted   <= {N{1'b0}};
      c_ro        <= {N{1'b0}};
      c_size      <= {(N*32){1'b0}};
      c_open_seen <= {N{1'b0}};
    end else begin
      tgl_s1 <= m_tgl;
      tgl_s2 <= tgl_s1;
      for (n = 0; n < N; n = n + 1) begin
        if (tgl_s2[n] != tgl_seen[n]) begin
          // the record was written >= 2 clk_sys cycles before the toggle
          // reached here and only changes on the next mount event, so a
          // plain copy across the domains is safe
          tgl_seen[n]        <= tgl_s2[n];
          c_mounted[n]       <= m_mounted[n];
          c_ro[n]            <= m_ro[n];
          c_size[n*32 +: 32] <= m_size[n*32 +: 32];
        end
        if (open_req[n]) c_open_seen[n] <= 1'b1;
      end
    end
  end

  // open semantics (spec: open_req pulse -> open_ok / open_err LEVELS).
  // There is nothing to "open": the file is whatever the OSD mounted. So
  // open_ok simply reports the mount, and open_err reports "you asked and
  // there is no file", which clears itself the moment one is mounted.
  assign open_ok    = c_mounted;
  assign open_err   = c_open_seen & ~c_mounted;
  assign size_bytes = c_size;
  assign mounted    = c_mounted;

  // ---- per-client request latch + round-robin arbiter --------------------
  reg [N-1:0]    pend;
  reg [N-1:0]    pend_wr;
  reg [N*16-1:0] pend_blk;
  reg [N*4-1:0]  code_q;         // err_code per client, valid with done

  assign err_code = code_q;

  localparam integer SW = (N <= 2) ? 1 : (N <= 4) ? 2 : (N <= 8) ? 3 : 4;
  reg [SW-1:0] rr;               // next slot to look at first
  reg [SW-1:0] g;                // granted client
  reg          g_act;            // a grant is being processed

  // the pick: first pending client at or after rr, wrapping
  reg [SW-1:0] pick;
  reg          pick_ok;
  integer k, m;
  always @(*) begin
    pick    = {SW{1'b0}};
    pick_ok = 1'b0;
    m       = 0;
    for (k = N - 1; k >= 0; k = k - 1) begin
      m = k + {{(32-SW){1'b0}}, rr};
      if (m >= N) m = m - N;
      if (pend[m]) begin
        pick    = m[SW-1:0];
        pick_ok = 1'b1;
      end
    end
  end

  assign busy = pend;

  // ---- client-side FSM ---------------------------------------------------
  localparam [2:0] C_IDLE   = 3'd0,  // pick a pending client, check it
                   C_PULL   = 3'd1,  // write: client buffer -> wbuf
                   C_GO     = 3'd2,  // hand to the QNICE side, wait h_done
                   C_STREAM = 3'd3,  // read: rbuf -> client buffer
                   C_DONE   = 3'd4;  // pulse done (+err)

  reg [2:0]  c_state;
  reg        g_wr;
  reg [15:0] g_blk;
  reg [10:0] idx;                // word walk, 0..1024
  reg        g_err;
  reg [3:0]  g_code;

  // the granted client's block-end check, 32-bit bytes
  wire [31:0] g_size    = c_size[g*32 +: 32];
  wire [31:0] blk_start = {5'd0, g_blk, 11'd0};        // block * 2048
  wire [31:0] blk_end   = blk_start + 32'd2048;

  // buffer mastering, one client at a time (all others parked at 0 so the
  // per-controller OR in the aggregators stays clean)
  reg [9:0]  o_addr;
  reg        o_we;
  reg [15:0] o_wdata;            // registered with o_we: same-cycle pair
  wire [15:0] g_rdata = buf_rdata[g*16 +: 16];

  genvar gc;
  generate
    for (gc = 0; gc < N; gc = gc + 1) begin : g_bus
      assign buf_addr [gc*10 +: 10] = (g_act && (g == gc)) ? o_addr : 10'd0;
      assign buf_wdata[gc*16 +: 16] = (g_act && (g == gc)) ? o_wdata : 16'd0;
      assign buf_we   [gc]          = (g_act && (g == gc)) ? o_we    : 1'b0;
    end
  endgenerate

  // rbuf registered read for the STREAM walk: address idx this cycle, word
  // out next cycle, written to the client at address idx-1
  //
  // wbuf write for the PULL walk. o_addr <= idx is registered, so the
  // client SEES address idx one cycle later, and its buffer read is itself
  // registered (every adapter: nd_storage_floppy_adapter's c_buf_rdata <=
  // s_blkbuf[c_buf_addr]; the disc adapter passes the controller's
  // registered dbuf_rdata straight through), so the word for address idx
  // is on buf_rdata TWO cycles after idx was set. Hence wbuf[idx-2].
  always @(posedge clk_cpu) begin
    rbuf_q <= {rbuf_hi[idx[9:0]], rbuf_lo[idx[9:0]]};
    if ((c_state == C_PULL) && (idx >= 11'd2)) wbuf[idx[9:0] - 10'd2] <= g_rdata;
  end

  always @(posedge clk_cpu or negedge rst_cpu_n) begin
    if (!rst_cpu_n) begin
      pend     <= {N{1'b0}};
      pend_wr  <= {N{1'b0}};
      pend_blk <= {(N*16){1'b0}};
      code_q   <= {(N*4){1'b0}};
      done     <= {N{1'b0}};
      err      <= {N{1'b0}};
      rr       <= {SW{1'b0}};
      g        <= {SW{1'b0}};
      g_act    <= 1'b0;
      g_wr     <= 1'b0;
      g_blk    <= 16'd0;
      idx      <= 11'd0;
      g_err    <= 1'b0;
      g_code   <= `NDS_ERR_NONE;
      o_addr   <= 10'd0;
      o_we     <= 1'b0;
      o_wdata  <= 16'd0;
      t_go     <= 1'b0;
      t_wr     <= 1'b0;
      t_lba    <= 32'd0;
      t_sel    <= {N{1'b0}};
      hd_s     <= 2'b00;
      c_state  <= C_IDLE;
    end else begin
      done <= {N{1'b0}};
      err  <= {N{1'b0}};
      o_we <= 1'b0;
      hd_s <= {hd_s[0], h_done};

      // latch requests (spec: a req while busy may be ignored - it is)
      for (n = 0; n < N; n = n + 1) begin
        if (req[n] && !pend[n]) begin
          pend[n]              <= 1'b1;
          pend_wr[n]           <= wr[n];
          pend_blk[n*16 +: 16] <= block[n*16 +: 16];
        end
      end

      case (c_state)
        C_IDLE: begin
          if (pick_ok) begin
            g      <= pick;
            g_act  <= 1'b1;
            g_wr   <= pend_wr[pick];
            g_blk  <= pend_blk[pick*16 +: 16];
            idx    <= 11'd0;
            g_err  <= 1'b0;
            g_code <= `NDS_ERR_NONE;
            // advance the scan past the winner: no starvation
            rr     <= ({{(32-SW){1'b0}}, pick} == N - 1) ? {SW{1'b0}} : pick + 1'b1;
            c_state <= C_GO;   // provisional; the checks below override
            // the three refusals, each with its reason
            if (!c_mounted[pick]) begin
              g_err <= 1'b1; g_code <= `NDS_ERR_NOTOPEN; c_state <= C_DONE;
            end else if (pend_wr[pick] && c_ro[pick]) begin
              g_err <= 1'b1; g_code <= `NDS_ERR_WRPROT;  c_state <= C_DONE;
            end else if (pend_wr[pick]) begin
              c_state <= C_PULL;  // range check follows in C_PULL's first cycle
            end
          end
        end

        // ---- write: walk the client buffer into wbuf, 1 word/cycle ------
        // (the range check sits here so g_* are registered when it runs)
        C_PULL: begin
          if ((idx == 11'd0) && (blk_end > g_size)) begin
            // a write must lie entirely inside the file: the QNICE firmware trims a
            // write past EOF and the image cannot grow through the core
            g_err <= 1'b1; g_code <= `NDS_ERR_RANGE; c_state <= C_DONE;
          end else begin
            if (idx < 11'd1024) o_addr <= idx[9:0];
            idx    <= idx + 11'd1;
            if (idx == 11'd1025) begin
              // the word for address 1023 landed this cycle (see the wbuf
              // write above); now hand the block to the QNICE side
              t_wr    <= 1'b1;
              t_lba   <= {14'd0, g_blk, 2'b00};   // block * 4 HPS blocks
              t_sel   <= {{(N-1){1'b0}}, 1'b1} << g;
              t_go    <= 1'b1;
              c_state <= C_GO;
            end
          end
        end

        // ---- reads arrive here straight from C_IDLE ----------------------
        C_GO: begin
          if (!t_go && !g_wr) begin
            if (blk_start >= g_size) begin
              // the block starts past the end of the file
              g_err <= 1'b1; g_code <= `NDS_ERR_RANGE; c_state <= C_DONE;
            end else begin
              t_wr  <= 1'b0;
              t_lba <= {14'd0, g_blk, 2'b00};
              t_sel <= {{(N-1){1'b0}}, 1'b1} << g;
              t_go  <= 1'b1;
            end
          end else if (hd_s[1]) begin
            // the QNICE side is finished: release the handshake
            t_go <= 1'b0;
            idx  <= 11'd0;
            if (g_wr) c_state <= C_DONE;
            else      c_state <= C_STREAM;
          end
        end

        // ---- read: walk rbuf into the client buffer, 1 word/cycle --------
        C_STREAM: begin
          // rbuf_q holds word idx-1 this cycle (registered read above);
          // address, data and strobe are registered TOGETHER so the client
          // sees them in the same cycle (the strobe alone registered, with
          // the data taken live, delivered word k+1 at address k - caught by
          // nd_storage_hps_tb test 3)
          idx <= idx + 11'd1;
          if (idx != 11'd0) begin
            o_addr  <= idx[9:0] - 10'd1;
            o_wdata <= rbuf_q;
            o_we    <= 1'b1;
          end
          if (idx == 11'd1024) c_state <= C_DONE;
        end

        C_DONE: begin
          // wait for the QNICE side to have dropped h_done before the next
          // request can raise go again (4-phase discipline)
          if (!hd_s[1]) begin
            done[g]            <= 1'b1;
            err[g]             <= g_err;
            code_q[g*4 +: 4]   <= g_err ? g_code : `NDS_ERR_NONE;
            pend[g]            <= 1'b0;
            g_act              <= 1'b0;
            o_addr             <= 10'd0;
            c_state            <= C_IDLE;
          end
        end

        default: c_state <= C_IDLE;
      endcase
    end
  end

endmodule