//========================================================================================
//  SUK.java
//    en:SCSI Ukun Kai
//    ja:すかじーU君改
//  Copyright (C) 2003-2024 Makoto Kamada
//
//  This file is part of the XEiJ (X68000 Emulator in Java).
//  You can use, modify and redistribute the XEiJ if the conditions are met.
//  Read the XEiJ License for more details.
//  https://stdkmd.net/xeij/
//========================================================================================

package xeij;

import java.awt.event.*;  //ActionEvent
import java.util.*;  //Arrays
import javax.swing.*;  //JMenu

import com.fazecast.jSerialComm.*;  //SerialPort

public class SUK {

  //レジスタ定数
  public static final int SPC_BDID          = 0x01;
  public static final int SPC_SCTL          = 0x03;
  public static final int SPC_SCTL_RD       = 0b10000000;
  public static final int SPC_SCTL_CR       = 0b01000000;
  public static final int SPC_SCTL_DM       = 0b00100000;
  public static final int SPC_SCTL_AE       = 0b00010000;
  public static final int SPC_SCTL_PE       = 0b00001000;
  public static final int SPC_SCTL_SE       = 0b00000100;
  public static final int SPC_SCTL_RE       = 0b00000010;
  public static final int SPC_SCTL_IE       = 0b00000001;
  public static final int SPC_SCMD          = 0x05;
  public static final int SPC_SCMD_CC       = 0b11100000;
  public static final int SPC_SCMD_CC_BR    = 0b00000000;
  public static final int SPC_SCMD_CC_SL    = 0b00100000;
  public static final int SPC_SCMD_CC_RA    = 0b01000000;
  public static final int SPC_SCMD_CC_SA    = 0b01100000;
  public static final int SPC_SCMD_CC_TR    = 0b10000000;
  public static final int SPC_SCMD_CC_TP    = 0b10100000;
  public static final int SPC_SCMD_CC_RR    = 0b11000000;
  public static final int SPC_SCMD_CC_SR    = 0b11100000;
  public static final int SPC_SCMD_RO       = 0b00010000;
  public static final int SPC_SCMD_IT       = 0b00001000;
  public static final int SPC_SCMD_PT       = 0b00000100;
  public static final int SPC_SCMD_TM       = 0b00000001;
  public static final int SPC_INTS          = 0x09;
  public static final int SPC_INTS_SL       = 0b10000000;
  public static final int SPC_INTS_RS       = 0b01000000;
  public static final int SPC_INTS_DC       = 0b00100000;
  public static final int SPC_INTS_CC       = 0b00010000;
  public static final int SPC_INTS_SR       = 0b00001000;
  public static final int SPC_INTS_TO       = 0b00000100;
  public static final int SPC_INTS_HE       = 0b00000010;
  public static final int SPC_INTS_RC       = 0b00000001;
  public static final int SPC_PSNS          = 0x0b;  //read
  public static final int SPC_PSNS_REQ      = 0b10000000;
  public static final int SPC_PSNS_ACK      = 0b01000000;
  public static final int SPC_PSNS_ATN      = 0b00100000;
  public static final int SPC_PSNS_SEL      = 0b00010000;
  public static final int SPC_PSNS_BSY      = 0b00001000;
  public static final int SPC_SDGC          = 0x0b;  //write
  public static final int SPC_SDGC_REQ      = 0b10000000;
  public static final int SPC_SDGC_ACK      = 0b01000000;
  public static final int SPC_SDGC_XFER     = 0b00100000;
  public static final int SPC_SDGC_BSY      = 0b00001000;
  public static final int SPC_SDGC_MSG      = 0b00000100;
  public static final int SPC_SDGC_CD       = 0b00000010;
  public static final int SPC_SDGC_IO       = 0b00000001;
  public static final int SPC_SSTS          = 0x0d;
  public static final int SPC_SSTS_INIT     = 0b10000000;
  public static final int SPC_SSTS_TARG     = 0b01000000;
  public static final int SPC_SSTS_BUSY     = 0b00100000;
  public static final int SPC_SSTS_TRIP     = 0b00010000;
  public static final int SPC_SSTS_RSIN     = 0b00001000;
  public static final int SPC_SSTS_TC0      = 0b00000100;
  public static final int SPC_SSTS_DF       = 0b00000010;
  public static final int SPC_SSTS_DE       = 0b00000001;
  public static final int SPC_SERR          = 0x0f;
  public static final int SPC_SERR_DI       = 0b10000000;
  public static final int SPC_SERR_DO       = 0b01000000;
  public static final int SPC_SERR_XO       = 0b00100000;
  public static final int SPC_SERR_PE       = 0b00001000;
  public static final int SPC_SERR_ST       = 0b00000010;
  public static final int SPC_PCTL          = 0x11;
  public static final int SPC_PCTL_IE       = 0b10000000;
  public static final int SPC_PCTL_SR       = 0b00000001;
  public static final int SPC_PCTL_SR_R     = 0b00000001;
  public static final int SPC_PCTL_SR_S     = 0b00000000;
  public static final int SPC_MBC           = 0x13;
  public static final int SPC_DREG          = 0x15;
  public static final int SPC_TEMP          = 0x17;
  public static final int SPC_TCH           = 0x19;
  public static final int SPC_TCM           = 0x1b;
  public static final int SPC_TCL           = 0x1d;
  public static final int SPC_BYPASS        = 0x1f;
  public static final int SPC_PHASE_MASK    = 0b00000111;
  public static final int SPC_DATAOUT_PHASE = 0b00000000;
  public static final int SPC_DATAIN_PHASE  = 0b00000001;
  public static final int SPC_CMDOUT_PHASE  = 0b00000010;
  public static final int SPC_STSIN_PHASE   = 0b00000011;
  public static final int SPC_MSGOUT_PHASE  = 0b00000110;
  public static final int SPC_MSGIN_PHASE   = 0b00000111;

  //レジスタ名
  public static final String[] SPC_REG_NAME = {
    "[0x00]", "BDID", "[0x02]", "SCTL", "[0x04]", "SCMD", "[0x06]", "[0x07]",
    "[0x08]", "INTS", "[0x0a]", "PSNS", "[0x0c]", "SSTS", "[0x0e]", "SERR",
    "[0x10]", "PCTL", "[0x12]", "MBC", "[0x14]", "DREG", "[0x16]", "TEMP",
    "[0x18]", "TCH", "[0x1a]", "TCM", "[0x1c]", "TCL", "[0x1e]", "BYPASS",
  };

  //フェーズ名
  public static final String[] SPC_PHASE_NAME = {
    "data-out-phase",  //0
    "data-in-phase",  //1
    "command-out-phase",  //2
    "status-in-phase",  //3
    "???",  //4
    "???",  //5
    "message-out-phase",  //6
    "message-in-phase",  //7
  };

  //設定
  public static final boolean SUK_ON = true;  //true=すかじーU君改に対応する
  public static boolean sukOnRequest;  //true=リセット後接続する
  public static boolean sukOn;  //true=接続している
  public static boolean sukExpansionRequest;  //true=リセット後拡張,false=リセット後内蔵
  public static boolean sukExpansion;  //true=拡張,false=内蔵

  //ポート
  public static final int SUK_VID = 0x04d8;  //ベンダーID
  public static final int SUK_PID = 0xe6b2;  //プロダクトID
  public static SerialPort sukPort1;  //ポート1
  public static SerialPort sukPort2;  //ポート2

  //レジスタ
  public static final byte[] sukRegister = new byte[32];  //最後に読み出されたか書き込まれたレジスタの値
  public static boolean sukReading;  //true=読み出し中

  //メニュー
  public static JMenu sukMenu;  //すかじーU君改メニュー
  public static JCheckBoxMenuItem sukConnectCheckBox;  //接続チェックボックス
  public static JCheckBoxMenuItem sukExpansionCheckBox;  //拡張チェックボックス
  public static JCheckBoxMenuItem sukDebugCheckBox;  //デバッグ出力チェックボックス
  public static JCheckBoxMenuItem sukDumpCheckBox;  //ダンプ出力チェックボックス

  //割り込み保証
  //  ポートがリードされたときデータの準備ができるまでコアを止めると、割り込みを含めてエミュレータ全体の動きが止まってしまう
  //  待機例外、待機ポイント、待機命令を用いてデータの準備ができるまで割り込みを止めずに待つ
  //
  //  MPUがリードしたポートのデータの準備ができていないときは、待機例外(新設、バスエラーの仲間)をスローして命令の実行を中止する
  //  待機例外をキャッチしたらPCとArを巻き戻して、PCの位置に待機ポイント(新設、命令ブレークポイントの仲間)を設置して続行する
  //  待機ポイントは踏まれると本来の命令コードではなく待機命令(新設、エミュレータ拡張命令)の命令コードを返す
  //  待機命令はBRA.S (*)と同様に割り込みを受け付けながら空ループを繰り返し、データの準備ができたら待機ポイントを撤去する
  //  待機ポイントがなくなるとポートをリードした命令がデータの準備ができている状態で再実行される
  //  複数のデータをリードすることがわかっている場合は一度に一定数のデータが揃うまで待つ
  //  バッファが空になったときはリードした命令の直後に待機ポイントを設置して再び一定数のデータが揃うまで待つ
  //  DMACによるリードはその場で待つことができないので、転送を開始した命令の直後に待機ポイントを設置してMTCで指定された数のデータが揃うまで待つ
  //  オートリクエスト最大速度だが、データが揃ってから転送を開始する

  //バイパス
  public static final boolean SUK_BYPASS = true;  //true=バイパスレジスタを使う
  public static final int SUK_BYPASS_AHEAD = 256;  //バイパス受信で先読みする長さ
  //  0x00000000  バイパス受信中ではない
  //  0xffffffff  バイパス受信中。次は長さ下位
  //  0x0000HHLL  バイパス受信中。次はデータ
  public static int sukBypassNotRead;
  //  0x00000000  バイパス送信中ではない
  //  0xffff00LL  バイパス送信中。次は長さ上位
  //  0x0000HHLL  バイパス送信中。次はデータ
  public static int sukBypassNotWritten;

  //sukInit ()
  //  初期化
  public static void sukInit () {
    sukOnRequest = Settings.sgsGetOnOff ("suk");
    sukOn = false;
    sukExpansionRequest = Settings.sgsGetOnOff ("sukex");
    sukExpansion = false;
    sukPort1 = null;
    sukPort2 = null;
    //sukRegister = new byte[32];
    if (SUK_DEBUG) {
      sukDebugInit ();
    }
    if (SUK_DUMP) {
      sukDumpInit ();
    }
  }  //sukInit

  //sukTini ()
  //  後始末
  public static void sukTini () {
    if (sukOn) {
      sukDisconnect ();
      sukOn = false;
    }
    Settings.sgsPutOnOff ("suk", sukOnRequest);
    Settings.sgsPutOnOff ("sukex", sukExpansionRequest);
    if (SUK_DEBUG) {
      sukDebugTini ();
    }
    if (SUK_DUMP) {
      sukDumpTini ();
    }
  }  //sukTini

  //sukGetMenu ()
  //  メニューを返す
  public static JMenu sukGetMenu () {
    if (sukMenu != null) {
      return sukMenu;
    }
    //アクションリスナー
    ActionListener actionListener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        Object source = ae.getSource ();
        String command = ae.getActionCommand ();
        switch (command) {
        case "Connect":  //接続
          sukOnRequest = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
          break;
        case "Expansion SCSI port":  //拡張 SCSI ポート
          sukExpansionRequest = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
          break;
        case "Debug output":  //デバッグ出力
          sukDebugOn = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
          break;
        case "Dump output":  //ダンプ出力
          sukDumpOn = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
          break;
        default:
          System.out.println ("unknown action command " + command);
        }
      }  //actionPerformed
    };  //actionListener
    //メニュー
    sukMenu = Multilingual.mlnText (
      ComponentFactory.createMenu (
        "SCSI Ukun Kai (experimental)",
        sukConnectCheckBox = Multilingual.mlnText (
          ComponentFactory.createCheckBoxMenuItem (sukOnRequest, "Connect", actionListener),
          "ja", "接続")
//        sukExpansionCheckBox = Multilingual.mlnText (
//          ComponentFactory.createCheckBoxMenuItem (sukExpansionRequest, "Expansion SCSI port", actionListener),
//          "ja", "拡張 SCSI ポート"),
//        !SUK_DEBUG ? null :
//        (sukDebugCheckBox = Multilingual.mlnText (
//          ComponentFactory.createCheckBoxMenuItem (sukDebugOn, "Debug output", actionListener),
//          "ja", "デバッグ出力")),
//        !SUK_DUMP ? null :
//        (sukDumpCheckBox = Multilingual.mlnText (
//          ComponentFactory.createCheckBoxMenuItem (sukDumpOn, "Dump output", actionListener),
//          "ja", "ダンプ出力"))
        ),
      "ja", "すかじー U 君改");
    return sukMenu;
  }  //sukGetMenu

  //sukReset ()
  //  リセット
  public static void sukReset () {
    if (sukOnRequest) {
      if (!sukConnect ()) {
        sukOnRequest = false;
        if (sukConnectCheckBox != null) {
          sukConnectCheckBox.setSelected (false);
        }
      }
    } else {
      sukDisconnect ();
    }
    sukOn = sukOnRequest;
    sukExpansion = sukExpansionRequest;
  }  //sukReset

  //sukConnect ()
  //  接続する
  public static boolean sukConnect () {
    if (sukPort1 != null) {  //接続している
      return true;
    }
    //すかじーU君改のポートを探す
    SerialPort port1 = null;
    SerialPort port2 = null;
    for (SerialPort port : SerialPort.getCommPorts ()) {  //すべてのシリアル通信ポートについて
      if (SUK_DEBUG && sukDebugOn) {
        System.out.printf ("systemPortName=%s vid=0x%04x pid=0x%04x\n",
                           port.getSystemPortName (),
                           port.getVendorID (),
                           port.getProductID ());
      }
      if (port.getVendorID () == SUK_VID &&  //ベンダーIDが一致
          port.getProductID () == SUK_PID) {  //プロダクトIDが一致
        if (port1 == null) {  //1個目
          port1 = port;  //ポート1(仮)
        } else if (port2 == null) {  //2個目
          port2 = port;  //ポート2(仮)
        } else {  //3個目
          //すかじーU君改が複数接続されている
          //ポート1とポート2の組み合わせが分からないので「先に見つかった方」は採用できない
          System.out.println (Multilingual.mlnJapanese ?
                              "すかじー U 君改のポートが多すぎます" :
                              "Too many MB89352 bridger");
          return false;
        }
      }
    }
    if (port2 == null) {  //見つからない
      System.out.println (Multilingual.mlnJapanese ?
                          "すかじー U 君改のポートが見つかりません" :
                          "MB89352 bridger not found");
      return false;
    }
    //ポートを開く
    if (!port1.openPort (0, 65536, 65536)) {  //ポート1(仮)を開けない
      System.out.println (Multilingual.mlnJapanese ?
                          "すかじー U 君改のポートを開けません" :
                          "Cannot open MB89352 bridger");
      return false;
    }
    if (!port2.openPort (0, 65536, 65536)) {  //ポート2(仮)を開けない
      port1.closePort ();
      System.out.println (Multilingual.mlnJapanese ?
                          "すかじー U 君改のポートを開けません" :
                          "Cannot open MB89352 bridger");
      return false;
    }
    //パラメータを設定する
    //  9600bpsに設定されているので480Mbpsに変更する
    port1.setComPortParameters (480000000, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);
    port2.setComPortParameters (480000000, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);
    //タイムアウトを設定する
    //  readBytesは50msを上限として少なくとも1バイト受信するまでブロックする
    //  writeBytesは送信バッファが一杯になるまでブロックしない
    port1.setComPortTimeouts (SerialPort.TIMEOUT_READ_BLOCKING, 50, 0);
    port2.setComPortTimeouts (SerialPort.TIMEOUT_READ_BLOCKING, 50, 0);
    //ポート1とポート2を判別する
    //  50ms以内に応答がないと失敗する
    byte[] b = new byte[] { 0x00, 0x00 };
    port1.writeBytes (b, 1, 0);  //ポート1(仮)へ0x00を送信する
    port1.readBytes (b, 1, 0);  //ポート1(仮)から1バイト受信する
    port2.writeBytes (b, 1, 1);  //ポート2(仮)へ0x00を送信する
    port2.readBytes (b, 1, 1);  //ポート2(仮)から1バイト受信する
    if (b[0] == 's' && b[1] == 'S') {  //(仮)は合っている
    } else if (b[0] == 'S' && b[1] == 's') {  //(仮)は逆
      //ポート1とポート2を入れ替える
      SerialPort port = port1;
      port1 = port2;
      port2 = port;
    } else {  //不明
      port1.closePort ();
      port2.closePort ();
      System.out.println (Multilingual.mlnJapanese ?
                          "すかじー U 君改のポートを判別できません" :
                          "Unable to identify MB89352 bridger");
      return false;
    }
    //タイムアウトを再設定する
    //  readBytesは指定された長さを受信するまでブロックする。以降はSerialPortEventのgetReceivedDataを使う
    //  writeBytesは送信バッファが一杯になるまでブロックしない
    port1.setComPortTimeouts (SerialPort.TIMEOUT_READ_BLOCKING, 0, 0);
    port2.setComPortTimeouts (SerialPort.TIMEOUT_READ_BLOCKING, 0, 0);
    //初期化する
    //  ポート1へ0x80,0xfeを送信する
    //  0xffのエスケープは用いない
    port1.writeBytes (new byte[] { (byte) 0x80 }, 1);
    port1.writeBytes (new byte[] { (byte) 0xfe }, 1);
    //完了
    sukPort1 = port1;
    sukPort2 = port2;
    Arrays.fill (sukRegister, (byte) 0x00);
    sukReading = false;
    System.out.printf (Multilingual.mlnJapanese ?
                       "すかじー U 君改 (%s,%s) に接続しました\n" :
                       "MB89352 bridger (%s,%s) connected\n",
                       sukPort1.getSystemPortName (),
                       sukPort2.getSystemPortName ());
    //受信キュー
    sukQueueInit ();
    //遅延送信
    sukPoolInit ();
    //連続受信
    sukDregInit ();
    if (SUK_BYPASS) {
      //バイパス
      sukBypassNotRead = 0;
      sukBypassNotWritten = 0;
    }
    //シリアルポートデータリスナーを登録する
    sukPort1.addDataListener (sukDataListener1);
    sukPort2.addDataListener (sukDataListener2);
    return true;
  }  //sukConnect

  //sukDisconnect ()
  //  切断する
  public static void sukDisconnect () {
    if (sukPort1 == null) {  //接続していない
      return;
    }
    //シリアルポートデータリスナーを解除する
    sukPort1.removeDataListener ();
    sukPort2.removeDataListener ();
    //ポートを閉じる
    System.out.printf (Multilingual.mlnJapanese ?
                       "すかじー U 君改 (%s,%s) を切り離しました\n" :
                       "MB89352 bridger (%s,%s) disconnected\n",
                       sukPort1.getSystemPortName (),
                       sukPort2.getSystemPortName ());
    sukPort1.clearRTS ();
    sukPort2.clearRTS ();
    sukPort1.clearDTR ();
    sukPort2.clearDTR ();
    sukPort1.closePort ();
    sukPort2.closePort ();
    sukPort1 = null;
    sukPort2 = null;
  }  //sukDisconnect

  //sukPeek (a)
  //  ピーク
  public static int sukPeek (int a) {
    a &= 0x1f;
    //最後に読み書きした値をそのまま返す
    return 0xff & sukRegister[a];
  }  //sukPeek

  //sukRead (a)
  //  リード
  public static int sukRead (int a) throws M68kException {
    a &= 0x1f;
    if (SUK_BYPASS) {
      if (0 < sukBypassNotRead) {  //バイパス受信中
        if (a != SPC_BYPASS) {  //バイパス受信中にバイパス以外のレジスタから読み出そうとした
          System.out.printf ("%08x sukRead() attempted to read from a register other than bypass during receiving bypass\n", XEiJ.regPC0);
        }
        int d = sukQueueData ();
        if (d < 0) {  //受信キューが空。あってはならない
          System.out.printf ("%08x sukRead() queue is empty\n", XEiJ.regPC0);
          d = 0;
        }
        if (SUK_DEBUG && sukDebugOn) {
          System.out.printf ("%08x sukRead() bypass data=0x%02x\n", XEiJ.regPC0, d);
        }
        sukBypassNotRead--;
        if (sukBypassNotRead == 0) {  //バイパス受信が終了した
          if (SUK_DEBUG && sukDebugOn) {
            System.out.printf ("%08x sukRead() sukBypassNotRead=%d\n", XEiJ.regPC0, sukBypassNotRead);
          }
        } else if ((sukQueueWrite - sukQueueRead) == 0) {  //バイパス受信が終了していなくて受信キューが空
          //直後の命令に待機ポイントを設置してデータを先読みする
          sukQueueRequired = Math.min (SUK_BYPASS_AHEAD, sukBypassNotRead);
          TickerQueue.tkqAdd (sukQueueTicker, XEiJ.mpuClockTime);
        }
        return d;
      }
      if (a == SPC_BYPASS) {
        if (sukBypassNotRead == 0) {  //バイパス受信中ではない
          sukBypassNotRead = -1;  //次は長さ下位
          //リードコマンドを送信する
          sukPoolAdd1 (a);  //リードコマンド
          sukPoolFlush ();
          //リードしようとした命令に待機ポイントを設置する
          //  データは少なくとも1バイト必要
          sukQueueRequired = 3;  //長さ下位,長さ上位,データ1バイト目
          InstructionBreakPoint.ibpAddWaitPoint (XEiJ.regPC0, XEiJ.regSRS, sukQueueInstruction);
          if (SUK_DEBUG && sukDebugOn) {
            System.out.printf ("%08x sukRead() queue instruction at pc=0x%08x required=%d\n", XEiJ.regPC0, XEiJ.regPC0, sukQueueRequired);
          }
          //待機例外をスローしてデータの準備ができてからリードし直す
          M68kException.m6eNumber = M68kException.M6E_WAIT_EXCEPTION;
          throw M68kException.m6eSignal;
        }
        if (sukBypassNotRead < 0) {  //バイパス受信中。次は長さ下位
          int l = sukQueueData ();  //長さ下位
          int h = sukQueueData ();  //長さ上位
          int d = sukQueueData ();  //データ1バイト目
          if (d < 0) {  //受信キューが空。あってはならない
            System.out.printf ("%08x sukRead() queue is empty\n", XEiJ.regPC0);
            l = 1;
            h = 0;
            d = 0;
          }
          sukBypassNotRead = h << 8 | l;
          if (SUK_DEBUG && sukDebugOn) {
            System.out.printf ("%08x sukRead() bypass lower=0x%02x\n", XEiJ.regPC0, l);
            System.out.printf ("%08x sukRead() bypass upper=0x%02x\n", XEiJ.regPC0, h);
            System.out.printf ("%08x sukRead() sukBypassNotRead=0x%02x\n", XEiJ.regPC0, sukBypassNotRead);
            System.out.printf ("%08x sukRead() bypass data=0x%02x\n", XEiJ.regPC0, d);
          }
          sukBypassNotRead--;
          if (sukBypassNotRead == 0) {  //バイパス受信が終了した
            if (SUK_DEBUG && sukDebugOn) {
              System.out.printf ("%08x sukRead() sukBypassNotRead=%d\n", XEiJ.regPC0, sukBypassNotRead);
            }
          } else if ((sukQueueWrite - sukQueueRead) == 0) {  //バイパス受信が終了していなくて受信キューが空
            //直後の命令に待機ポイントを設置してデータを先読みする
            sukQueueRequired = Math.min (SUK_BYPASS_AHEAD, sukBypassNotRead);
            TickerQueue.tkqAdd (sukQueueTicker, XEiJ.mpuClockTime);
          }
          return d;
        }
        //次はデータ
      }
    }
    if ((sukDregLength - sukDregRead) != 0) {  //連続リード中
      if (a == SPC_DREG) {  //DREG
        //バッファからデータを読み出す
        if ((sukDregWrite - sukDregRead) == 0) {  //バッファが空。あってはならない
          System.out.printf ("%08x sukRead() buffer is empty\n", XEiJ.regPC0);
          return 0xff & sukRegister[a];
        }
        int d = 0xff & sukDregBuffer[sukDregRead++];
        if ((sukDregLength - sukDregRead) == 0) {  //連続リードが終了した
          //TCH,TCM,TCLを0にする
          sukRegister[SPC_TCH] = 0x00;
          sukRegister[SPC_TCM] = 0x00;
          sukRegister[SPC_TCL] = 0x00;
          if (SUK_DEBUG && sukDebugOn) {
            sukDebugRegister (true, SPC_TCH);
            sukDebugRegister (true, SPC_TCM);
            sukDebugRegister (true, SPC_TCL);
            System.out.printf ("%08x sukRead() sukDregRead=%d\n", XEiJ.regPC0, sukDregRead);
          }
        }
        sukRegister[a] = (byte) d;
        //次のデータを準備する
        if ((XEiJ.busWaitTime == XEiJ.mpuWaitTime ||
             XEiJ.busWaitTime == XEiJ.mpuNoWaitTime) &&  //MPUによるリードで
            (sukDregLength - sukDregRead) != 0 &&  //連続リードが終了しておらず
            (sukDregWrite - sukDregRead) == 0) {  //バッファから読み出せる長さが0のとき
          //直後の命令に必要な長さのデータが揃うまで待つ待機ポイントを設置する
          //  MPUからリードされた時点でPCが直後の命令を指しているとは限らないのでティッカーを挟んで間接的に待機ポイントを設置する
          sukDregRequired = Math.min (SUK_MPU_AHEAD, sukDregLength - sukDregRead);
          sukDregCounter = 0;
          TickerQueue.tkqAdd (sukDregMPUTicker, XEiJ.mpuClockTime);
        }
        if (SUK_DUMP && sukDumpOn) {
          sukDumpAdd (d);
        }
      } else if (a == SPC_INTS) {  //INTSは常に0x00
        sukRegister[a] = 0x00;
      } else if (a == SPC_SSTS) {  //SSTSのTC0とDEは0、DFは未リードデータ数が8未満のとき0、さもなくば1
        sukRegister[a] = (byte) (SPC_SSTS_INIT | SPC_SSTS_BUSY | SPC_SSTS_TRIP | ((sukDregLength - sukDregRead) < 8 ? 0 : SPC_SSTS_DF));
      } else if (a == SPC_TCH) {  //TCH,TCM,TCLは未リードデータ数
        sukRegister[a] = (byte) ((sukDregLength - sukDregRead) >> 16);
      } else if (a == SPC_TCM) {
        sukRegister[a] = (byte) ((sukDregLength - sukDregRead) >> 8);
      } else if (a == SPC_TCL) {
        sukRegister[a] = (byte) (sukDregLength - sukDregRead);
      }
      if (SUK_DEBUG && sukDebugOn) {
        sukDebugRegister (true, a);
      }
      return 0xff & sukRegister[a];
    }
    //連続リード中ではない
    if (XEiJ.busWaitTime == XEiJ.dmaWaitTime ||
        XEiJ.busWaitTime == XEiJ.dmaNoWaitTime) {  //DMACによるリード。あってはならない
      System.out.printf ("%08x sukRead() DMAC attempted to read while not in data-in-phase\n", XEiJ.regPC0);
      return 0xff & sukRegister[a];
    }
    //MPUによるリード
    if (a == SPC_DREG) {  //DREGのとき
      if (!sukDregStandby) {  //連続リード以外の方法でDREGをリードしようとした。あってはならない
        System.out.printf ("%08x sukRead() MPU attempted to read while not in data-in-phase\n", XEiJ.regPC0);
      }
      //連続受信を開始する
      sukDregStart ();
      //MPUによる連続受信を開始する
      int d = sukDregMPUStart ();  //データがないので待機例外をスローするはず
      sukRegister[a] = (byte) d;
      if (SUK_DEBUG && sukDebugOn) {
        sukDebugRegister (true, a);
      }
      if (SUK_DUMP && sukDumpOn) {
        sukDumpAdd (d);
      }
      return d;
    }
    if (!sukReading) {  //読み出し中ではない
      sukReading = true;  //読み出し開始
      //リードコマンドを送信する
      sukPoolAdd1 (a);  //リードコマンド
      sukPoolFlush ();
      //リードしようとした命令に待機ポイントを設置する
      sukQueueRequired = 1;
      InstructionBreakPoint.ibpAddWaitPoint (XEiJ.regPC0, XEiJ.regSRS, sukQueueInstruction);
      if (false) {
        if (SUK_DEBUG && sukDebugOn) {
          System.out.printf ("%08x sukRead() queue instruction at pc=0x%08x required=%d\n", XEiJ.regPC0, XEiJ.regPC0, sukQueueRequired);
        }
      }
      //待機例外をスローしてデータの準備ができてからリードし直す
      M68kException.m6eNumber = M68kException.M6E_WAIT_EXCEPTION;
      throw M68kException.m6eSignal;
    }
    //読み出し中
    //受信キューからデータを読み出す
    int d = sukQueueData ();
    if (d < 0) {  //受信キューが空。あってはならない
      System.out.printf ("%08x sukRead() queue is empty\n", XEiJ.regPC0);
      return 0xff & sukRegister[a];
    }
    //SSTSが0x00のとき0x05または0x01に読み替える
    //  SSTS(0x00e9602d)はSASI内蔵機とSCSI内蔵機の判別に使われる
    //  DFとDEは同時にセットされないのでSSTSは0xffにならない
    //  BUSYでないときも(TC0と)DEはセットされるのでSSTSは0x00にならない
    //  IPLROM 1.6はSSTSを見て実機の0xffとX68000_MiSTerの0x00をSASI内蔵機、さもなくばSCSI内蔵機とみなす
    //  すかじーU君改はSSTSが0x00を返すのでSASI内蔵機とみなされ、SCSIINROMがあるので構成に誤りがあると判断されて起動できない
    if (a == SPC_SSTS && d == 0x00) {
      d = ((sukRegister[SPC_TCH] | sukRegister[SPC_TCM] | sukRegister[SPC_TCL]) == 0 ? SPC_SSTS_TC0 : 0) | SPC_SSTS_DE;
    }
    sukRegister[a] = (byte) d;
    if (SUK_DEBUG && sukDebugOn) {
      sukDebugRegister (true, a);
    }
    if (SUK_DUMP && sukDumpOn) {
      if (a == SPC_TEMP) {
        int psns = 0xff & sukRegister[SPC_PSNS];
        if (psns == (SPC_PSNS_ACK | SPC_PSNS_BSY | SPC_DATAIN_PHASE) ||
            psns == (SPC_PSNS_ACK | SPC_PSNS_BSY | SPC_STSIN_PHASE) ||
            psns == (SPC_PSNS_ACK | SPC_PSNS_BSY | SPC_MSGIN_PHASE)) {
          sukDumpAdd (0xff & sukRegister[a]);
        }
      }
    }
    sukReading = false;  //読み出し終了
    return d;
  }  //sukRead

  //sukWrite (a, d)
  //  ライト
  public static void sukWrite (int a, int d) {
    a &= 0x1f;
    d &= 0xff;
    if (SUK_BYPASS) {
      if (sukBypassNotWritten != 0) {  //バイパス送信中
        if (a != SPC_BYPASS) {  //バイパス送信中にバイパス以外のレジスタに書き込もうとした
          System.out.printf ("%08x sukWrite() attempted to write to a register other than bypass during bypass transmission\n", XEiJ.regPC0);
        }
        sukPoolAdd1 (d);
        if (sukBypassNotWritten < 0) {  //次は長さ上位
          sukBypassNotWritten = d << 8 | (0xff & sukBypassNotWritten);  //長さ
          if (SUK_DEBUG && sukDebugOn) {
            System.out.printf ("%08x sukWrite() bypass upper=0x%02x\n", XEiJ.regPC0, d);
            System.out.printf ("%08x sukWrite() sukBypassNotWritten=%d\n", XEiJ.regPC0, sukBypassNotWritten);
          }
        } else {  //次はデータ
          if (SUK_DEBUG && sukDebugOn) {
            System.out.printf ("%08x sukWrite() bypass data=0x%02x\n", XEiJ.regPC0, d);
          }
          sukBypassNotWritten--;
          if (sukBypassNotWritten == 0) {  //バイパス送信が終了した
            if (SUK_DEBUG && sukDebugOn) {
              System.out.printf ("%08x sukWrite() sukBypassNotWritten=%d\n", XEiJ.regPC0, sukBypassNotWritten);
            }
          }
        }
        return;
      }
    }
    sukRegister[a] = (byte) d;
    if (SUK_DEBUG && sukDebugOn) {
      sukDebugRegister (false, a);
    }
    //ライトコマンドを送信する
    sukPoolAdd2 (0x80 + a, d);  //ライトコマンドとデータ
    if (SUK_BYPASS) {
      if (a == SPC_BYPASS) {  //バイパス送信開始
        sukBypassNotWritten = 0xffff0000 + d;  //長さ下位
        if (SUK_DEBUG && sukDebugOn) {
          System.out.printf ("%08x sukWrite() bypass lower=0x%02x\n", XEiJ.regPC0, d);
        }
      }
    }
    if (SUK_DUMP && sukDumpOn) {
      if (a == SPC_DREG) {
        sukDumpAdd (d);
      } else if (a == SPC_TEMP) {
        int psns = 0xff & sukRegister[SPC_PSNS];
        if (psns == (SPC_PSNS_REQ | SPC_PSNS_BSY | SPC_DATAOUT_PHASE) ||
            psns == (SPC_PSNS_REQ | SPC_PSNS_BSY | SPC_CMDOUT_PHASE) ||
            psns == (SPC_PSNS_REQ | SPC_PSNS_BSY | SPC_MSGOUT_PHASE)) {
          sukDumpAdd (d);
        }
      } else if (a == SPC_PCTL) {
        sukDumpDump (d & SPC_PHASE_MASK);
      }
    }
    //間隔保証
    int us = (a == SPC_SCMD && (d & SPC_SCMD_RO) != 0 ? 250 :  //SCSIバスリセット開始
              a == SPC_SCMD && (d & SPC_SCMD_CC) == SPC_SCMD_CC_SL ? 50 :  //セレクション開始
              a == SPC_INTS && (d & SPC_INTS_TO) != 0 ? 50 :  //セレクションタイムアウトクリア
              0);
    if (us != 0) {  //実時間で間隔を空ける必要があるとき
      sukPoolFlush ();
      //直後の命令に目標のナノ秒時刻になるまで待つ待機ポイントを設置する
      sukIntervalTime = System.nanoTime () + us * 1000;
      sukIntervalCounter = 0;
      InstructionBreakPoint.ibpAddWaitPoint (XEiJ.regPC, XEiJ.regSRS, sukIntervalInstruction);
      if (SUK_DEBUG && sukDebugOn) {
        System.out.printf ("%08x sukWrite() interval instruction at pc=0x%08x time=%d\n", XEiJ.regPC0, XEiJ.regPC, sukIntervalTime);
      }
    }
    //連続受信
    if ((sukRegister[SPC_PCTL] & SPC_PHASE_MASK) == SPC_DATAIN_PHASE &&  //データインフェーズで
        a == SPC_SCMD && (d & SPC_SCMD_CC) == SPC_SCMD_CC_TR) {  //受信を開始したとき
      sukDregStandby = true;  //スタンバイ開始
      if (SUK_DEBUG && sukDebugOn) {
        System.out.printf ("%08x sukWrite() SPC has started receiving\n", XEiJ.regPC0);
      }
    }
  }  //sukWrite



  //データリスナー

  //sukDataListener1
  //  ポート1データリスナー
  //  getReceivedData()について
  //    LISTENING_EVENT_DATA_AVAILABLEのときgetReceivedData()はnullを返す
  //    LISTENING_EVENT_DATA_RECEIVEDのときgetReceivedData()は受信バッファを先読みして返す
  //    受信バッファを先読みするだけで取り除かないのでこれだけでは2回目のイベントが発生しない
  public static final SerialPortDataListener sukDataListener1 = new SerialPortDataListener () {
    @Override public int getListeningEvents () {
      return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;
    }  //getListeningEvents
    @Override public void serialEvent (SerialPortEvent spe) {
      if (spe.getEventType () != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) {
        return;
      }
      //データを受信する
      int n = sukPort1.bytesAvailable ();
      byte[] b = new byte[n];
      sukPort1.readBytes (b, n);
      int w = sukQueueWrite;  //受信キューに書き込んだ長さ
      int r = sukQueueRead;  //受信キューから読み出した長さ
      //受信した長さが受信キューに書き込める長さを超えていてはならない
      if ((SUK_QUEUE_SIZE - (w - r)) < n) {
        System.out.printf ("%08x sukDataListener1.serialEvent() queue is full\n", XEiJ.regPC0);
        return;
      }
      //受信したデータを受信キューに移す
      int i = w & (SUK_QUEUE_SIZE - 1);  //受信キューに書き込む位置
      int k = Math.min (n, SUK_QUEUE_SIZE - i);  //1回目に書き込む長さ
      System.arraycopy (b, 0,  //from
                        sukQueueArray, i,  //to
                        k);  //length
      if (k < n) {  //受信キューの末尾と先頭を跨ぐとき
        System.arraycopy (b, k,  //from
                          sukQueueArray, 0,  //to
                          n - k);  //length
      }
      sukQueueWrite = w += n;
    }  //serialEvent
  };  //sukDataListener1

  //sukDataListener2
  //  ポート2データリスナー
  public static final SerialPortDataListener sukDataListener2 = new SerialPortDataListener () {
    @Override public int getListeningEvents () {
      return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;
    }  //getListeningEvents
    @Override public void serialEvent (SerialPortEvent spe) {
      if (spe.getEventType () != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) {
        return;
      }
      //データを受信する
      byte[] b = spe.getReceivedData ();
      if (b == null) {
        return;
      }
      int n = b.length;  //受信した長さ
      for (int i = 0; i < n; i++) {
        if (b[i] == 'I') {  //割り込み要求
          if (SUK_DEBUG && sukDebugOn) {
            System.out.printf ("%08x serialEvent() interrupt\n", XEiJ.regPC0);
          }
          if (sukExpansion) {
            XEiJ.eb2Interrupt (XEiJ.EB2_SPC_REQUEST);
          } else {
            IOInterrupt.ioiSpcFall ();
            IOInterrupt.ioiSpcRise ();
          }
        }
      }
    }  //serialEvent
  };  //sukDataListener2



  //受信キュー
  //  ポート1から受信したデータは直ちに受信キューに移される
  //  受信キューは溢れてはならない
  public static final int SUK_QUEUE_SIZE = 1048576;  //キューの長さ。1MB
  public static final byte[] sukQueueArray = new byte[SUK_QUEUE_SIZE];  //キューの配列
  public static volatile int sukQueueWrite;  //書き込んだ長さ
  public static volatile int sukQueueRead;  //読み出した長さ
  public static int sukQueueRequired;  //今回必要な長さ

  //sukQueueInit ()
  //  初期化
  public static void sukQueueInit () {
    //sukQueueArray = new byte[SUK_QUEUE_SIZE];
    sukQueueWrite = 0;
    sukQueueRead = 0;
    sukQueueRequired = 0;
  }  //sukQueueInit

  //d = sukQueueData ()
  //  受信キューからデータを読み出す
  //  d  読み出したデータ。-1=受信キューが空
  public static int sukQueueData () {
    int w = sukQueueWrite;
    int r = sukQueueRead;
    if ((w - r) == 0) {  //受信キューが空
      return -1;
    }
    int d = 0xff & sukQueueArray[r++ & (SUK_QUEUE_SIZE - 1)];
    sukQueueRead = r;
    return d;
  }  //sukQueueData

  //sukQueueTicker
  //  直後の命令に必要な長さのデータが揃うまで待つ待機ポイントを設置するティッカー
  public static final TickerQueue.Ticker sukQueueTicker = new TickerQueue.Ticker () {
    @Override protected void tick () {
      //sukQueueRequiredは設定済み
      //ティッカーが動く前に割り込み要因が生じている可能性があるが割り込み受け付けは後なのでPCは直後の命令を指している
      int pc = XEiJ.regPC;
      InstructionBreakPoint.ibpAddWaitPoint (pc, XEiJ.regSRS, sukQueueInstruction);
      if (SUK_DEBUG && sukDebugOn) {
        int required = sukQueueRequired;
        System.out.printf ("%08x sukQueueTicker.tick() queue instruction at pc=0x%08x required=%d\n", XEiJ.regPC0, pc, required);
      }
    }  //tick
  };  //sukQueueTicker

  //sukQueueInstruction
  //  今回必要な長さのデータを受信するまで待つ待機命令
  public static final WaitInstruction sukQueueInstruction = new WaitInstruction () {
    @Override public boolean terminate () {
      return sukQueueRequired <= (sukQueueWrite - sukQueueRead);
    }  //terminate
  };  //sukQueueInstruction



  //連続受信と連続リード
  //  0x41によるDREGの連続受信と連続リード
  //  連続受信と同時に連続リードを開始する
  //  連続受信が終了した後もバッファが空になるまで連続リードは続く
  public static boolean sukDregStandby;  //true=スタンバイ。SPCの転送開始からMPUまたはDMACの受信開始まで
  public static int sukDregLength;  //全体の長さ
  public static int sukDregMajor;  //全体の残りの長さ
  public static int sukDregMinor;  //ブロックの残りの長さ。-1=ブロックの長さを受信中。sukDregMinor!=0のとき連続受信中
  public static byte[] sukDregBuffer;  //バッファ
  public static int sukDregWrite;  //バッファに書き込んだ長さ
  public static int sukDregRead;  //バッファから読み出した長さ。(sukDregLength-sukDregRead)!=0のとき連続リード中
  public static int sukDregRequired;  //今回必要な長さ
  public static int sukDregCounter;  //バッファから読み出せる長さを求める頻度を下げるためのカウンタ

  //sukDregInit ()
  //  初期化
  public static void sukDregInit () {
    sukDregStandby = false;
    sukDregLength = 0;
    sukDregMajor = 0;
    sukDregMinor = 0;
    sukDregBuffer = new byte[0];
    sukDregWrite = 0;
    sukDregRead = 0;
    sukDregRequired = 0;
    sukDregCounter = 0;
  }  //sukDregInit

  //sukDregStart ()
  //  連続受信を開始する
  //  スタンバイから最初に
  //    MPUがDREGをリードしたとき
  //    または
  //    DAR1がDREGかつOCR1のDIRがDEVICE_TO_MEMORYでCCR1にSTRが書き込まれたとき
  //  開始
  //    スタンバイ終了
  //    全体の長さはTCH<<16|TCM<<8|TCL
  //    全体の残りの長さは全体の長さ、ブロックの残りの長さは0
  //    全体の長さのバッファを確保する
  //    バッファに書き込んだ長さとバッファから読み出した長さを0にする
  //    全体の長さが0でないとき
  //      ブロックの残りの長さを-1にする
  //      0x41を送信する
  public static void sukDregStart () {
    //スタンバイ終了
    sukDregStandby = false;
    //全体の長さはTCH<<16|TCM<<8|TCL
    sukDregLength = ((0xff & sukRegister[SPC_TCH]) << 16 |
                     (0xff & sukRegister[SPC_TCM]) << 8 |
                     (0xff & sukRegister[SPC_TCL]));
    //全体の残りの長さは全体の長さ、ブロックの残りの長さは0
    sukDregMajor = sukDregLength;
    sukDregMinor = 0;
    if (SUK_DEBUG && sukDebugOn) {
      System.out.printf ("%08x sukDregStart() sukDregLength=%d\n", XEiJ.regPC0, sukDregLength);
      System.out.printf ("%08x sukDregStart() sukDregMajor=%d\n", XEiJ.regPC0, sukDregMajor);
      System.out.printf ("%08x sukDregStart() sukDregMinor=%d\n", XEiJ.regPC0, sukDregMinor);
    }
    //全体の長さのバッファを確保する
    if (sukDregBuffer.length < sukDregLength) {  //入り切らないとき
      sukDregBuffer = new byte[sukDregLength];  //新しく作る
    }
    //バッファに書き込んだ長さとバッファから読み出した長さを0にする
    sukDregWrite = 0;
    sukDregRead = 0;
    if (SUK_DEBUG && sukDebugOn) {
      System.out.printf ("%08x sukDregStart() sukDregWrite=%d\n", XEiJ.regPC0, sukDregWrite);
      System.out.printf ("%08x sukDregStart() sukDregRead=%d\n", XEiJ.regPC0, sukDregRead);
    }
    //全体の長さが0でないとき
    if (sukDregLength != 0) {
      //ブロックの残りの長さを-1にする
      sukDregMinor = -1;
      if (SUK_DEBUG && sukDebugOn) {
        System.out.printf ("%08x sukDregStart() sukDregMinor=%d\n", XEiJ.regPC0, sukDregMinor);
      }
      //0x41を送信する
      sukPort1.writeBytes (new byte[] { 0x41 }, 1);
    }
  }  //sukDregStart

  //available = sukDregAvailable ()
  //  バッファを更新してバッファから読み出せる長さを求める
  //    ブロックの残りの長さが-1かつ受信キューから読み出せる長さが2以上のとき
  //      受信キューからブロックの長さを読み出す
  //      ブロックの長さが全体の残りの長さを超えていてはならない
  //      全体の残りの長さからブロックの長さを引く
  //    ブロックの残りの長さが1以上かつ受信キューから読み出せる長さが1以上のとき
  //      受信キューから読み出せる長さがブロックの残りの長さを超えていてはならない
  //      受信キューから読み出せる長さのデータを読み出してバッファに移す
  //      移した長さを受信キューから読み出した長さに加える
  //      移した長さをブロックの残りの長さから引く
  //      移した長さをバッファに書き込んだ長さに加える
  //    ブロックの残りの長さが0かつ全体の残りの長さが0でないとき
  //      ブロックの残りの長さを-1にする
  //      0x41を送信する
  //  available  バッファから読み出せる長さ
  public static int sukDregAvailable () {
    int w = sukQueueWrite;  //受信キューに書き込んだ長さ
    int r = sukQueueRead;  //受信キューから読み出した長さ
    //ブロックの残りの長さが-1かつ受信キューから読み出せる長さが2以上のとき
    if (sukDregMinor == -1 && 2 <= (w - r)) {
      //受信キューからブロックの長さを読み出す
      int l = 0xff & sukQueueArray[r++ & (SUK_QUEUE_SIZE - 1)];
      int h = 0xff & sukQueueArray[r++ & (SUK_QUEUE_SIZE - 1)];
      sukQueueRead = r;
      sukDregMinor = h << 8 | l;
      if (SUK_DEBUG && sukDebugOn) {
        System.out.printf ("%08x sukDregAvailable() sukDregMinor=%d\n", XEiJ.regPC0, sukDregMinor);
      }
      //ブロックの長さが全体の残りの長さを超えていてはならない
      if (sukDregMajor < sukDregMinor) {
        System.out.printf ("%08x sukDregAvailable() total overrun\n", XEiJ.regPC0);
        sukDregMinor = sukDregMajor;  //!!!
        if (SUK_DEBUG && sukDebugOn) {
          System.out.printf ("%08x sukDregAvailable() sukDregMinor=%d\n", XEiJ.regPC0, sukDregMinor);
        }
      }
      //全体の残りの長さからブロックの長さを引く
      sukDregMajor -= sukDregMinor;
      if (SUK_DEBUG && sukDebugOn) {
        System.out.printf ("%08x sukDregAvailable() sukDregMajor=%d\n", XEiJ.regPC0, sukDregMajor);
      }
    }
    int n = w - r;  //受信キューから読み出せる長さ
    //ブロックの残りの長さが1以上かつ受信キューから読み出せる長さが1以上のとき
    if (1 <= sukDregMinor && 1 <= n) {
      //受信キューから読み出せる長さがブロックの残りの長さを超えていてはならない
      if (sukDregMinor < n) {
        System.out.printf ("%08x sukDregAvailable() block overrun\n", XEiJ.regPC0);
        n = sukDregMinor;  //!!!
      }
      //受信キューから読み出せる長さのデータを読み出してバッファに移す
      int i = r & (SUK_QUEUE_SIZE - 1);  //受信キューから読み出す位置
      int k = Math.min (n, SUK_QUEUE_SIZE - i);  //1回目に読み出す長さ
      System.arraycopy (sukQueueArray, i,  //from
                        sukDregBuffer, sukDregWrite,  //to
                        k);  //length
      if (k < n) {  //受信キューの末尾と先頭を跨ぐとき
        System.arraycopy (sukQueueArray, 0,  //from
                          sukDregBuffer, sukDregWrite + k,  //to
                          n - k);  //length
      }
      //移した長さを受信キューから読み出した長さに加える
      sukQueueRead = r += n;
      //移した長さをブロックの残りの長さから引く
      sukDregMinor -= n;
      if (SUK_DEBUG && sukDebugOn) {
        System.out.printf ("%08x sukDregAvailable() sukDregMinor=%d\n", XEiJ.regPC0, sukDregMinor);
      }
      //移した長さをバッファに書き込んだ長さに加える
      sukDregWrite += n;
      if ((sukDregLength - sukDregWrite) == 0) {  //連続受信が終了した
        if (SUK_DEBUG && sukDebugOn) {
          System.out.printf ("%08x sukDregAvailable() sukDregWrite=%d\n", XEiJ.regPC0, sukDregWrite);
        }
      }
    }
    //ブロックの残りの長さが0かつ全体の残りの長さが0でないとき
    if (sukDregMinor == 0 && sukDregMajor != 0) {
      //ブロックの残りの長さを-1にする
      sukDregMinor = -1;
      if (SUK_DEBUG && sukDebugOn) {
        System.out.printf ("%08x sukDregAvailable() sukDregMinor=%d\n", XEiJ.regPC0, sukDregMinor);
      }
      //0x41を送信する
      sukPort1.writeBytes (new byte[] { 0x41 }, 1);
    }
    return sukDregWrite - sukDregRead;
  }  //sukDregAvailable



  //MPUによる連続受信
  public static final int SUK_MPU_AHEAD = 256;  //MPUによる連続受信で先読みする長さ

  //d = sukDregMPUStart ()
  //  MPUによる連続受信を開始する
  public static int sukDregMPUStart () throws M68kException {
    if (SUK_DEBUG && sukDebugOn) {
      System.out.printf ("%08x sukDregMPUStart() MPU has started receiving\n", XEiJ.regPC0);
    }
    int required = Math.min (SUK_MPU_AHEAD, sukDregLength - sukDregRead);
    if (required <= (sukDregWrite - sukDregRead)) {  //足りている。ないはず
      int d = 0xff & sukDregBuffer[sukDregRead++];
      if ((sukDregLength - sukDregRead) == 0) {  //連続リードが終了した
        if (SUK_DEBUG && sukDebugOn) {
          System.out.printf ("%08x sukDregMPUStart() sukDregRead=%d\n", XEiJ.regPC0, sukDregRead);
        }
      }
      return d;
    }
    //リードしようとした命令に待機ポイントを設置する
    sukDregRequired = required;
    sukDregCounter = 0;
    InstructionBreakPoint.ibpAddWaitPoint (XEiJ.regPC0, XEiJ.regSRS, sukDregMPUInstruction);
    if (SUK_DEBUG && sukDebugOn) {
      System.out.printf ("%08x sukDregMPUStart() pc=0x%08x required=%d\n", XEiJ.regPC0, XEiJ.regPC0, required);
    }
    //待機例外をスローしてデータの準備ができてからリードし直す
    M68kException.m6eNumber = M68kException.M6E_WAIT_EXCEPTION;
    throw M68kException.m6eSignal;
  }  //sukDregMPUStart

  //sukDregMPUTicker
  //  直後の命令に必要な長さのデータが揃うまで待つ待機ポイントを設置するティッカー
  public static final TickerQueue.Ticker sukDregMPUTicker = new TickerQueue.Ticker () {
    @Override protected void tick () {
      //sukDregRequiredは設定済み
      //ティッカーが動く前に割り込み要因が生じている可能性があるが割り込み受け付けは後なのでPCは直後の命令を指している
      int pc = XEiJ.regPC;
      InstructionBreakPoint.ibpAddWaitPoint (pc, XEiJ.regSRS, sukDregMPUInstruction);
      if (SUK_DEBUG && sukDebugOn) {
        int required = sukDregRequired;
        System.out.printf ("%08x sukDregMPUTicker.tick() dreg mpu instruction at pc=0x%08x required=%d\n", XEiJ.regPC0, pc, required);
      }
    }  //tick
  };  //sukDregMPUTicker

  //sukDregMPUInstruction
  //  今回必要な長さのデータが揃うまで待つ待機命令
  public static final WaitInstruction sukDregMPUInstruction = new WaitInstruction () {
    @Override public boolean terminate () {
      return ((++sukDregCounter & 15) == 0 &&
              sukDregRequired <= sukDregAvailable ());
    }  //terminate
  };  //sukDregMPUInstruction



  //DMACによる連続受信
  public static final int SUK_DREG_CHANNEL = 1;  //使用するDMACのチャンネル

  //sukDregDMACStart ()
  //  DMACによる連続受信を開始または継続する
  public static void sukDregDMACStart () {
    if (sukDregStandby) {  //スタンバイのとき
      if (SUK_DEBUG && sukDebugOn) {
        System.out.printf ("%08x sukDregDMACStart() DMAC has started receiving\n", XEiJ.regPC0);
      }
      //連続受信を開始する
      sukDregStart ();
    }
    if ((sukDregLength - sukDregRead) == 0) {  //DMACが連続リード中でないときにリードしようとした。あってはならない
      System.out.printf ("%08x sukDregDMACStart() DMAC attempted to read while not in data-in-phase\n", XEiJ.regPC0);
      //!!!
    }
    int required = HD63450.dmaMTC[SUK_DREG_CHANNEL];  //今回必要な長さ
    if (required <= (sukDregWrite - sukDregRead)) {  //足りている
      HD63450.dmaTransfer (SUK_DREG_CHANNEL);  //最初のデータを転送する
    } else {  //足りていない
      //直後の命令に待機ポイントを設置する
      int pc = XEiJ.regPC;
      sukDregRequired = required;
      sukDregCounter = 0;
      InstructionBreakPoint.ibpAddWaitPoint (pc, XEiJ.regSRS, sukDregDMACInstruction);
      if (SUK_DEBUG && sukDebugOn) {
        System.out.printf ("%08x sukDregDMACStart() dreg dmac instruction at pc=0x%08x required=%d\n", XEiJ.regPC0, XEiJ.regPC, required);
      }
    }
  }  //sukDregDMACStart

  //sukDregDMACInstruction
  //  今回必要な長さのデータが揃うまで待ってから最初のデータを転送する待機命令
  public static final WaitInstruction sukDregDMACInstruction = new WaitInstruction () {
    @Override public boolean terminate () {
      boolean b = ((++sukDregCounter & 15) == 0 &&
                   sukDregRequired <= sukDregAvailable ());
      if (b) {
        HD63450.dmaTransfer (SUK_DREG_CHANNEL);
      }
      return b;
    }  //terminate
  };  //sukDregDMACInstruction



  //遅延送信
  //  送信データを送信プールに溜めて一定時間送信がないか受信を行うときにまとめて送信する
  //  送信と受信の間隔が足りなくなる場合は個別に間隔保証を行うこと
  //  ティッカーはコアと同じスレッドで命令の後に呼び出されるので追加や削除で衝突する心配はない
  public static final int SUK_POOL_SIZE = 511;  //送信プールのサイズ。511以下
  public static final long SUK_POOL_SPAN = XEiJ.TMR_FREQ / 1000;  //一定時間。1ms
  public static final byte[] sukPoolBuffer = new byte[SUK_POOL_SIZE];  //送信プール
  public static int sukPoolLength;  //送信プールにあるデータの長さ

  //sukPoolInit ()
  //  初期化
  public static void sukPoolInit () {
    //sukPoolBuffer = new byte[SUK_POOL_SIZE];
    sukPoolLength = 0;
  }  //sukPoolInit

  //sukPoolAdd1 (d)
  //  送信プールにデータを1個加える
  public static void sukPoolAdd1 (int d1) {
    if (SUK_POOL_SIZE - 1 < sukPoolLength) {
      sukPoolFlush ();
    }
    sukPoolBuffer[sukPoolLength++] = (byte) d1;
    TickerQueue.tkqAdd (sukPoolTicker, XEiJ.mpuClockTime + SUK_POOL_SPAN);
  }  //sukPoolAdd1

  //sukPoolAdd2 (d1, d2)
  //  送信プールにデータを2個加える
  public static void sukPoolAdd2 (int d1, int d2) {
    if (SUK_POOL_SIZE - 2 < sukPoolLength) {
      sukPoolFlush ();
    }
    sukPoolBuffer[sukPoolLength++] = (byte) d1;
    sukPoolBuffer[sukPoolLength++] = (byte) d2;
    TickerQueue.tkqAdd (sukPoolTicker, XEiJ.mpuClockTime + SUK_POOL_SPAN);
  }  //sukPoolAdd2

  //sukPoolTicker
  //  最後の送信から一定時間後に送信プールから送信するティッカー
  public static final TickerQueue.Ticker sukPoolTicker = new TickerQueue.Ticker () {
    @Override protected void tick () {
      //送信プールから送信する
      sukPoolFlush ();
    }  //tick
  };  //sukPoolTicker

  //sukPoolFlush ()
  //  送信プールから送信する
  public static void sukPoolFlush () {
    if (0 < sukPoolLength) {
      sukPort1.writeBytes (sukPoolBuffer, sukPoolLength);
      sukPoolLength = 0;
    }
  }  //sukPoolFlush



  //間隔保証
  //  仮想時間で間隔を空ける命令列(Timer-Cを使う方法を含む)は実時間で間隔を空けることができない
  //  ポートに書き込んでから次に読み書きするまでに実時間で間隔を空ける必要があるとき、最初の書き込みの直後に待機ポイントを設置する
  //  最初の書き込みだけでは間隔が必要かどうか分からない場合は余分に間隔が空く場合がある
  //  待機ポイントで間隔を空けるのでその後に実行される命令列と合わせると必要以上に間隔が空く場合がある
  public static long sukIntervalTime;  //目標のナノ秒時刻
  public static int sukIntervalCounter;  //ナノ秒時刻を確認する頻度を下げるためのカウンタ

  //sukIntervalInstruction
  //  目標のナノ秒時刻になるまで待つ待機命令
  public static final WaitInstruction sukIntervalInstruction = new WaitInstruction () {
    @Override public boolean terminate () {
      return ((++sukIntervalCounter & 15) == 0 &&
              0 <= (System.nanoTime () - sukIntervalTime));
    }  //terminate
  };  //sukIntervalInstruction



  //デバッグ出力
  public static final boolean SUK_DEBUG = true;  //true=デバッグ出力あり
  public static boolean sukDebugOn;  //true=デバッグ出力あり
  public static final byte[] sukDebugLastRegister = new byte[32];  //最後に表示したレジスタの値

  //sukDebugInit ()
  //  初期化
  public static void sukDebugInit () {
    sukDebugOn = Settings.sgsGetOnOff ("sukdebug");
    //sukDebugLastRegister = new byte[32]
  }  //sukDebugInit

  //sukDebugTini ()
  //  後始末
  public static void sukDebugTini () {
    Settings.sgsPutOnOff ("sukdebug", sukDebugOn);
  }  //sukDebugTini

  //sukDebugRegister (read, a)
  //  レジスタを表示する
  public static void sukDebugRegister (boolean read, int a) {
    if (a != SPC_DREG && a != SPC_BYPASS &&  //DREG/BYPASS以外
        sukDebugLastRegister[a] != sukRegister[a]) {  //最後に表示した値と異なる
      System.out.printf ("%08x suk%s() %s=0x%02x\n", XEiJ.regPC0, read ? "Read" : "Write", SPC_REG_NAME[a], 0xff & sukRegister[a]);
      sukDebugLastRegister[a] = sukRegister[a];
    }
  }  //sukDebugRegister



  //ダンプ出力
  //  転送データをダンプする
  public static final boolean SUK_DUMP = true;  //true=ダンプ出力あり
  public static boolean sukDumpOn;  //true=ダンプ出力あり
  public static final int SUK_DUMP_LIMIT = 4096;  //ダンプするデータの長さの上限
  public static final byte[] sukDumpBuffer = new byte[SUK_DUMP_LIMIT];  //ダンプするデータのバッファ
  public static int sukDumpLength;  //ダンプするデータの長さ
  public static int sukDataPhase;  //ダンプするデータのフェーズ

  //sukDumpInit ()
  //  初期化
  public static void sukDumpInit () {
    sukDumpOn = Settings.sgsGetOnOff ("sukdump");
    //sukDumpBuffer = new byte[4096];
    sukDumpLength = 0;
    sukDataPhase = -1;
  }  //sukDumpInit

  //sukDumpTini ()
  //  後始末
  public static void sukDumpTini () {
    Settings.sgsPutOnOff ("sukdump", sukDumpOn);
  }  //sukDumpTini

  //sukDumpAdd (d)
  //  データをバッファに追加する
  public static void sukDumpAdd (int d) {
    sukDumpDump (sukRegister[SPC_PSNS] & SPC_PHASE_MASK);
    if (sukDumpLength < SUK_DUMP_LIMIT) {
      sukDumpBuffer[sukDumpLength++] = (byte) d;
    }
  }  //sukDumpAdd

  //sukDumpDump (phase)
  //  フェーズが変化したらデータをダンプする
  public static void sukDumpDump (int phase) {
    if (sukDataPhase != phase) {
      if (0 <= sukDataPhase) {
        System.out.printf ("%08x %s\n", XEiJ.regPC0, SPC_PHASE_NAME[sukDataPhase]);
        int m = (sukDumpLength + 15) & -16;
        for (int i = 0; i < m; i += 16) {
          System.out.printf ("%08x ", i);
          for (int j = 0; j < 16; j++) {
            if (i + j < sukDumpLength) {
              System.out.printf ("%02x ", 0xff & sukDumpBuffer[i + j]);
            } else {
              System.out.print ("   ");
            }
          }
          for (int j = 0; j < 16; j++) {
            if (i + j < sukDumpLength) {
              if (0x20 <= sukDumpBuffer[i + j] && sukDumpBuffer[i + j] <= 0x7e) {
                System.out.printf ("%c", 0xff & sukDumpBuffer[i + j]);
              } else {
                System.out.print (".");
              }
            } else {
              System.out.print (" ");
            }
          }
          System.out.println ();
        }
      }
      if (sukDataPhase == SPC_MSGIN_PHASE && phase == SPC_DATAOUT_PHASE) {
        sukDataPhase = -1;
      } else {
        sukDataPhase = phase;
      }
      sukDumpLength = 0;
    }
  }  //sukDumpDump



}  //class SUK
