MAMEVECTOR64その2

DeepSeek、Gemini Notebookによるシリアル通信の改善

// license:BSD-3-Clause
// copyright-holders:Brad Oliver,Aaron Giles,Bernd Wiebelt,Allard van der Bas
/******************************************************************************
 *
 * vector.c
 *
 *        anti-alias code by Andrew Caldwell
 *        (still more to add)
 *
 * 040227 Fixed miny clip scaling which was breaking in mhavoc. AREK
 * 010903 added support for direct RGB modes MLR
 * 980611 use translucent vectors. Thanks to Peter Hirschberg
 *        and Neil Bradley for the inspiration. BW
 * 980307 added cleverer dirty handling. BW, ASG
 *        fixed antialias table .ac
 * 980221 rewrote anti-alias line draw routine
 *        added inline assembly multiply fuction for 8086 based machines
 *        beam diameter added to draw routine
 *        beam diameter is accurate in anti-alias line draw (Tcosin)
 *        flicker added .ac
 * 980203 moved LBO's routines for drawing into a buffer of vertices
 *        from avgdvg.c to this location. Scaling is now initialized
 *        by calling vector_init(...). BW
 * 980202 moved out of msdos.c ASG
 * 980124 added anti-alias line draw routine
 *        modified avgdvg.c and sega.c to support new line draw routine
 *        added two new tables Tinten and Tmerge (for 256 color support)
 *        added find_color routine to build above tables .ac
 *
 **************************************************************************** */

#include "emu.h"
#include "emuopts.h"
#include "rendutil.h"
#include "vector.h"

// Serial port related includes
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>

#include <inttypes.h>
#include <sys/time.h>
 

#define FLT_EPSILON 1E-5

#define VECTOR_WIDTH_DENOM 512

#define MAX_POINTS 10000

#define VECTOR_SERIAL_MAX 4095

#define VECTOR_TEAM \
   "-* Vector Heads *-\n" \
   "Brad Oliver\n" \
   "Aaron Giles\n" \
   "Bernd Wiebelt\n" \
   "Allard van der Bas\n" \
   "Al Kossow (VECSIM)\n" \
   "Hedley Rainnie (VECSIM)\n" \
   "Eric Smith (VECSIM)\n" \
   "Neil Bradley (technical advice)\n" \
   "Andrew Caldwell (anti-aliasing)\n" \
   "- *** -\n"

///-----
#ifdef __cplusplus > 201711L
  #define TERMIWIN_MAYBE_UNUSED [[maybe_unused]]
#else
  #ifdef __GNUC__
    #define TERMIWIN_MAYBE_UNUSED __attribute__((unused))
  #else
    #define TERMIWIN_MAYBE_UNUSED
  #endif
#endif

#include <fcntl.h>
#include <stdlib.h>

typedef struct COM {
  HANDLE hComm;
  int fd; //Actually it's completely useless
  char port[128];
} COM;

DCB SerialParams = { 0 }; //Initializing DCB structure
struct COM com;
COMMTIMEOUTS timeouts = { 0 }; //Initializing COMMTIMEOUTS structure

//LOCAL functions

//nbyte 0->7

int getByte(tcflag_t flag, int nbyte, int nibble) {

  int byte;
  if (nibble == 1)
    byte = (flag >> (8 * (nbyte)) & 0x0f);
  else
    byte = (flag >> (8 * (nbyte)) & 0xf0);
  return byte;
}

//INPUT FUNCTIONS

enum{
  i_IXOFF = 0x01,
  i_IXON = 0x02,
  i_IXOFF_IXON = 0x03,
  i_PARMRK = 0x04,
  i_PARMRK_IXOFF = 0x05,
  i_PARMRK_IXON = 0x06,
  i_PARMRK_IXON_IXOFF = 0x07
};

int getIXOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);

  return byte;
}

//LOCALOPT FUNCTIONS

enum{
  l_NOECHO = 0x00,
  l_ECHO = 0x01,
  l_ECHO_ECHOE = 0x03,
  l_ECHO_ECHOK = 0x05,
  l_ECHO_ECHONL = 0x09,
  l_ECHO_ECHOE_ECHOK = 0x07,
  l_ECHO_ECHOE_ECHONL = 0x0b,
  l_ECHO_ECHOE_ECHOK_ECHONL = 0x0f,
  l_ECHO_ECHOK_ECHONL = 0x0d,
  l_ECHOE = 0x02,
  l_ECHOE_ECHOK = 0x06,
  l_ECHOE_ECHONL = 0x0a,
  l_ECHOE_ECHOK_ECHONL = 0x0e,
  l_ECHOK = 0x04,
  l_ECHOK_ECHONL = 0x0c,
  l_ECHONL = 0x08
};

int getEchoOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);
  return byte;
}

enum{
  l_ICANON = 0x10,
  l_ICANON_ISIG = 0x50,
  l_ICANON_IEXTEN = 0x30,
  l_ICANON_NOFLSH = 0x90,
  l_ICANON_ISIG_IEXTEN = 0x70,
  l_ICANON_ISIG_NOFLSH = 0xd0,
  l_ICANON_IEXTEN_NOFLSH = 0xb0,
  l_ICANON_ISIG_IEXTEN_NOFLSH = 0xf0,
  l_ISIG = 0x40,
  l_ISIG_IEXTEN = 0x60,
  l_ISIG_NOFLSH = 0xc0,
  l_ISIG_IEXTEN_NOFLSH = 0xe0,
  l_IEXTEN = 0x20,
  l_IEXTEN_NOFLSH = 0xa0,
  l_NOFLSH = 0x80,
};

int getLocalOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 0);
  return byte;
}

enum{
  l_TOSTOP = 0x01
};

int getToStop(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);
  return byte;
}

//CONTROLOPT FUNCTIONS

int getCharSet(tcflag_t flag) {

  //FLAG IS MADE UP OF 8 BYTES, A FLAG IS MADE UP OF A NIBBLE -> 4 BITS, WE NEED TO EXTRACT THE SECOND NIBBLE (1st) FROM THE FIFTH BYTE (6th).
  int byte = getByte(flag, 1, 1);

  switch (byte) {

  case 0X0:
    return CS5;
    break;

  case 0X4:
    return CS6;
    break;

  case 0X8:
    return CS7;
    break;

  case 0Xc:
    return CS8;
    break;

  default:
    return CS8;
    break;
  }
}

enum{
  c_ALL_ENABLED = 0xd0,
  c_PAREVEN_CSTOPB = 0x50,
  c_PAREVEN_NOCSTOPB = 0x40,
  c_PARODD_NOCSTOPB = 0xc0,
  c_NOPARENB_CSTOPB = 0x10,
  c_ALL_DISABLED = 0x00,
};

int getControlOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 0);
  return byte;
}

//LIBFUNCTIONS

int tcgetattr(int fd, struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  if (fd != com.fd) return -1;
  int TERMIWIN_MAYBE_UNUSED ret = 0;

  ret = GetCommState(com.hComm, &SerialParams);

  return 0;
}

int tcsetattr(int fd, int TERMIWIN_MAYBE_UNUSED optional_actions, const struct termios* termios_p) {

  if (fd != com.fd) return -1;
  int ret = 0;

  //Store flags into local variables
  tcflag_t iflag = termios_p->c_iflag;
  tcflag_t lflag = termios_p->c_lflag;
  tcflag_t cflag = termios_p->c_cflag;
  tcflag_t TERMIWIN_MAYBE_UNUSED oflag = termios_p->c_oflag;

  //iflag

  int IX = getIXOptions(iflag);

  if ((IX == i_IXOFF_IXON) || (IX == i_PARMRK_IXON_IXOFF)) {

    SerialParams.fOutX = TRUE;
    SerialParams.fInX = TRUE;
    SerialParams.fTXContinueOnXoff = TRUE;
  }

  //lflag
  int TERMIWIN_MAYBE_UNUSED EchoOpt = getEchoOptions(lflag);
  int TERMIWIN_MAYBE_UNUSED l_opt = getLocalOptions(lflag);
  int TERMIWIN_MAYBE_UNUSED tostop = getToStop(lflag);

  //Missing parameters...

  //cflags

  int CharSet = getCharSet(cflag);
  int c_opt = getControlOptions(cflag);

  switch (CharSet) {

  case CS5:
    SerialParams.ByteSize = 5;
    break;

  case CS6:
    SerialParams.ByteSize = 6;
    break;

  case CS7:
    SerialParams.ByteSize = 7;
    break;

  case CS8:
    SerialParams.ByteSize = 8;
    break;
  }

  switch (c_opt) {

  case c_ALL_ENABLED:
    SerialParams.Parity = ODDPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;

  case c_ALL_DISABLED:
    SerialParams.Parity = NOPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_PAREVEN_CSTOPB:
    SerialParams.Parity = EVENPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;

  case c_PAREVEN_NOCSTOPB:
    SerialParams.Parity = EVENPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_PARODD_NOCSTOPB:
    SerialParams.Parity = ODDPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_NOPARENB_CSTOPB:
    SerialParams.Parity = NOPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;
  }

  //aflags

  /*
  int OP;
  if(oflag == OPOST)
  else ...
  */
  //Missing parameters...

  //special characters

  if (termios_p->c_cc[VEOF] != 0) SerialParams.EofChar = (char)termios_p->c_cc[VEOF];
  if (termios_p->c_cc[VINTR] != 0) SerialParams.EvtChar = (char)termios_p->c_cc[VINTR];

  if (termios_p->c_cc[VMIN] == 1) { //Blocking

    timeouts.ReadIntervalTimeout = 0;         // in milliseconds
    timeouts.ReadTotalTimeoutConstant = 0;    // in milliseconds
    timeouts.ReadTotalTimeoutMultiplier = 0;  // in milliseconds
///    timeouts.WriteTotalTimeoutConstant = 0;   // in milliseconds
    timeouts.WriteTotalTimeoutConstant = 1000;   // in milliseconds
    timeouts.WriteTotalTimeoutMultiplier = 0; // in milliseconds

  } else { //Non blocking

    timeouts.ReadIntervalTimeout = termios_p->c_cc[VTIME] * 100;         // in milliseconds
    timeouts.ReadTotalTimeoutConstant = termios_p->c_cc[VTIME] * 100;    // in milliseconds
    timeouts.ReadTotalTimeoutMultiplier = termios_p->c_cc[VTIME] * 100;  // in milliseconds
///    timeouts.WriteTotalTimeoutConstant = termios_p->c_cc[VTIME] * 100;   // in milliseconds
    timeouts.WriteTotalTimeoutConstant = 1000;   // in milliseconds
///    timeouts.WriteTotalTimeoutMultiplier = termios_p->c_cc[VTIME] * 100; // in milliseconds
    timeouts.WriteTotalTimeoutMultiplier = 0; // in milliseconds
  }

  SetCommTimeouts(com.hComm, &timeouts);

  //EOF

  ret = SetCommState(com.hComm, &SerialParams);
  if (ret != 0)
    return 0;
  else
    return -1;
}

int tcsendbreak(int fd, int TERMIWIN_MAYBE_UNUSED duration) {

  if (fd != com.fd) return -1;

  int ret = 0;
  ret = TransmitCommChar(com.hComm, '\x00');
  if (ret != 0)
    return 0;
  else
    return -1;
}

int tcdrain(int fd) {

///  if (fd != com.fd) return -1;
///  return FlushFileBuffers(com.hComm);
    if (fd != com.fd) return -1;

    // FlushFileBuffers(com.hComm) を削除し、即座に成功(0)を返すようにします。
    // これにより、ハードウェア側の送信完了を待たずに次の処理へ進めるようになります。
    return 0; 
}

int tcflush(int fd, int queue_selector) {

  if (fd != com.fd) return -1;
  int rc = 0;

  switch (queue_selector) {

  case TCIFLUSH:
    rc = PurgeComm(com.hComm, PURGE_RXCLEAR);
    break;

  case TCOFLUSH:
    rc = PurgeComm(com.hComm, PURGE_TXCLEAR);
    break;

  case TCIOFLUSH:
    rc = PurgeComm(com.hComm, PURGE_RXCLEAR);
    rc *= PurgeComm(com.hComm, PURGE_TXCLEAR);
    break;

  default:
    rc = 0;
    break;
  }

  if (rc != 0)
    return 0;
  else
    return -1;
}

int tcflow(int fd, int action) {

  if (fd != com.fd) return -1;
  int rc = 0;

  switch (action) {

  case TCOOFF:
    rc = PurgeComm(com.hComm, PURGE_TXABORT);
    break;

  case TCOON:
    rc = ClearCommBreak(com.hComm);
    break;

  case TCIOFF:
    rc = PurgeComm(com.hComm, PURGE_RXABORT);
    break;

  case TCION:
    rc = ClearCommBreak(com.hComm);
    break;

  default:
    rc = 0;
    break;
  }

  if (rc != 0)
    return 0;
  else
    return -1;
}

void cfmakeraw(struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  SerialParams.ByteSize = 8;
  SerialParams.StopBits = ONESTOPBIT;
  SerialParams.Parity = NOPARITY;
}

speed_t cfgetispeed(const struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  return SerialParams.BaudRate;
}

speed_t cfgetospeed(const struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  return SerialParams.BaudRate;
}

int cfsetispeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

int cfsetospeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

int cfsetspeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

ssize_t read_serial(int fd, void* buffer, size_t count) {

  if (fd != com.fd) return -1;
///  int rc = 0;
  DWORD rc = 0;
  int ret;

  ret = ReadFile(com.hComm, buffer, count, &rc, NULL);

  if (ret == 0)
    return -1;
  else
    return rc;
}

ssize_t write_serial(int fd, const void* buffer, size_t count) {

  if (fd != com.fd) return -1;
///  int rc = 0;
  DWORD rc = 0;
  int ret;

  ret = WriteFile(com.hComm, buffer, count, &rc, NULL);

  if (ret == 0)
    return -1;
  else
    return rc;
}

int open_serial(const char* portname, int opt) {

  if (strlen(portname) < 4) return -1;

  // Set to zero
  memset(com.port, 0x00, 128);

  //COMxx
  size_t portSize = 0;
  if (strlen(portname) > 4) {
    portSize = sizeof(char) * strlen("\\\\.\\COM10") + 1;
#ifdef _MSC_VER
    strncat_s(com.port, portSize, "\\\\.\\", strlen("\\\\.\\"));
#else
    strncat(com.port, "\\\\.\\", strlen("\\\\.\\"));
#endif
  }
  //COMx
  else {
    portSize = sizeof(char) * 5;
  }

#ifdef _MSC_VER
  strncat_s(com.port, portSize, portname, 4);
#else
  strncat(com.port, portname, 4);
#endif
  com.port[portSize] = 0x00;

  switch (opt) {

  case O_RDWR:
    com.hComm = CreateFile(com.port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;

  case O_RDONLY:
    com.hComm = CreateFile(com.port, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;

  case O_WRONLY:
    com.hComm = CreateFile(com.port, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;
  }

  if (com.hComm == INVALID_HANDLE_VALUE) {
    return -1;
  }
  com.fd = atoi(portname + 3); // COMx and COMxx
  SerialParams.DCBlength = sizeof(SerialParams);
  return com.fd;
}

int close_serial(int TERMIWIN_MAYBE_UNUSED fd) {

  int ret = CloseHandle(com.hComm);
  if (ret != 0)
    return 0;
  else
    return -1;
}

int select_serial(int TERMIWIN_MAYBE_UNUSED nfds, fd_set* readfds, fd_set* TERMIWIN_MAYBE_UNUSED writefds, fd_set* TERMIWIN_MAYBE_UNUSED exceptfds, struct timeval* TERMIWIN_MAYBE_UNUSED timeout) {

    DWORD dwErrors;
    COMSTAT cs;

    // 現在のシリアルポートの状態を取得し、エラーをクリアする
    if (!ClearCommError(com.hComm, &dwErrors, &cs)) {
        return -1; // 失敗した場合はエラーを返す
    }

    // 受信バッファにデータ(cbInQue)があるか確認する
    if (cs.cbInQue > 0) {
        return com.fd; // データがあればファイル記述子(ポート番号)を返す
    } else {
        if (readfds) {
            // データがない場合は、呼び出し元のセットから記述子をクリアする
            FD_CLR(com.fd, readfds);
        }
    }

///  SetCommMask(com.hComm, EV_RXCHAR);
///  DWORD dwEventMask;
///  if (WaitCommEvent(com.hComm, &dwEventMask, NULL) == 0) {
///    return -1; // Return -1 if failed
///  }
///  if (dwEventMask == EV_RXCHAR) {
///    return com.fd;
///  } else {
///    if (readfds) {
      // Clear file descriptor if event is not RXCHAR
///      FD_CLR(com.fd, readfds);
///    }
///  }
  // NOTE: write event not detectable!
  // NOTE: no timeout
  return 0; // No data
}

//Returns hComm from the COM structure
HANDLE getHandle() {
  return com.hComm;
}
///-----

#define VCLEAN  0
#define VDIRTY  1
#define VCLIP   2

// device type definition
const device_type VECTOR = &device_creator<vector_device>;

vector_device::vector_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source)
    : device_t(mconfig, type, name, tag, owner, clock, shortname, source),
        device_video_interface(mconfig, *this),
        m_vector_list(nullptr),
        m_min_intensity(255),
        m_max_intensity(0)
{
}

vector_device::vector_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
    : device_t(mconfig, VECTOR, "VECTOR", tag, owner, clock, "vector_device", __FILE__),
        device_video_interface(mconfig, *this),
        m_vector_list(nullptr),
        m_min_intensity(255),
        m_max_intensity(0)
{
}

float vector_device::m_flicker = 0.0f;
float vector_device::m_beam_width_min = 0.0f;
float vector_device::m_beam_width_max = 0.0f;
float vector_device::m_beam_intensity_weight = 0.0f;
int vector_device::m_vector_index;

struct serial_segment_t {
    struct serial_segment_t * next;
    int intensity;
    int x0;
    int y0;
    int x1;
    int y1;

    serial_segment_t(
        int x0,
        int y0,
        int x1,
        int y1,
        int intensity
    ) :
        next(NULL),
        intensity(intensity),
        x0(x0),
        y0(y0),
        x1(x1),
        y1(y1)
    {
    }
};

int
serial_open(
        const char * const dev
)
{
///        const int fd = open(dev, O_RDWR | O_NONBLOCK | O_NOCTTY, 0666);
        const int fd = open(dev, O_RDWR, 0666);
        if (fd < 0)
                return -1;

        // Disable modem control signals
        struct termios attr;
        tcgetattr(fd, &attr);
        attr.c_cflag |= CLOCAL | CREAD;
        attr.c_oflag &= ~OPOST;
        tcsetattr(fd, TCSANOW, &attr);

        return fd;
}

void vector_device::serial_draw_point(
    unsigned x,
    unsigned y,
    int intensity
)
{
    // make sure that we are in range; should always be
    // due to clipping on the window, but just in case
    if (x < 0) x = 0;
    if (y < 0) y = 0;

    if (x > VECTOR_SERIAL_MAX) x = VECTOR_SERIAL_MAX;
    if (y > VECTOR_SERIAL_MAX) y = VECTOR_SERIAL_MAX;

    // always flip the Y, since the vectorscope measures
    // 0,0 at the bottom left corner, but this coord uses
    // the top left corner.
    y = VECTOR_SERIAL_MAX - y;

    unsigned bright;
    if (intensity > m_serial_bright)
        bright = 63;
    else
    if (intensity <= 0)
        bright = 0;
    else
        bright = (intensity * 64) / 256;

    if (bright > 63)
        bright = 63;

    if (m_serial_rotate == 1)
    {
        // +90
        unsigned tmp = x;
        x = VECTOR_SERIAL_MAX - y;
        y = tmp;
    } else
    if (m_serial_rotate == 2)
    {
        // +180
        x = VECTOR_SERIAL_MAX - x;
        y = VECTOR_SERIAL_MAX - y;
    } else
    if (m_serial_rotate == 3)
    {
        // -90
        unsigned t = x;
        x = y;
        y = VECTOR_SERIAL_MAX - t;
    }

    uint32_t cmd = 0
        | (2 << 30)
        | (bright & 0x3F) << 24
        | (x & 0xFFF) << 12
        | (y & 0xFFF) <<  0
        ;

    //printf("%08x %8d %8d %3d\n", cmd, x, y, intensity);

    m_serial_buf[m_serial_offset++] = cmd >> 24;
    m_serial_buf[m_serial_offset++] = cmd >> 16;
    m_serial_buf[m_serial_offset++] = cmd >>  8;
    m_serial_buf[m_serial_offset++] = cmd >>  0;

    // todo: check for overflow;
    // should always have enough points
}


// This will only be called with non-zero intensity lines.
// we keep a linked list of the vectors and sort them with
// a greedy insertion sort.
void vector_device::serial_draw_line(
    float xf0,
    float yf0,
    float xf1,
    float yf1,
    int intensity
)
{
    if (m_serial_fd < 0)
        return;

    // scale and shift each of the axes.
    const int x0 = (xf0 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_x + m_serial_offset_x;
    const int y0 = (yf0 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_y + m_serial_offset_y;
    const int x1 = (xf1 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_x + m_serial_offset_x;
    const int y1 = (yf1 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_y + m_serial_offset_y;

    serial_segment_t * const new_segment
        = new serial_segment_t(x0, y0, x1, y1, intensity);

    if (this->m_serial_segments_tail)
        this->m_serial_segments_tail->next = new_segment;
    else
        this->m_serial_segments = new_segment;

    this->m_serial_segments_tail = new_segment;
}


void vector_device::serial_reset()
{
    m_serial_offset = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;

    m_vector_transit[0] = 0;
    m_vector_transit[1] = 0;
    m_vector_transit[2] = 0;
}


void vector_device::serial_send()
{
    if (m_serial_fd < 0)
        return;

    int last_x = -1;
    int last_y = -1;

    // find the next closest point to the last one.
    // greedy sorting algorithm reduces beam transit time
    // fairly significantly. doesn't matter for the
    // vectorscope, but makes a big difference for Vectrex
    // and other slower displays.
    while(this->m_serial_segments)
    {
        int reverse = 0;
        int min = 1e6;
        serial_segment_t ** min_seg
            = &this->m_serial_segments;

        if (m_serial_sort)
        for(serial_segment_t ** s = min_seg ; *s ; s = &(*s)->next)
        {
            int dx0 = (*s)->x0 - last_x;
            int dy0 = (*s)->y0 - last_y;
            int dx1 = (*s)->x1 - last_x;
            int dy1 = (*s)->y1 - last_y;
            int d0 = sqrt(dx0*dx0 + dy0*dy0);
            int d1 = sqrt(dx1*dx1 + dy1*dy1);

            if(d0 < min)
            {
                min_seg = s;
                min = d0;
                reverse = 0;
            }

            if (d1 < min)
            {
                min_seg = s;
                min = d1;
                reverse = 1;
            }

            // if we have hit two identical points,
            // then stop the search here.
            if (min == 0)
                break;
        }

        serial_segment_t * const s = *min_seg;
        if (!s)
            break;
    
        const int x0 = reverse ? s->x1 : s->x0;
        const int y0 = reverse ? s->y1 : s->y0;
        const int x1 = reverse ? s->x0 : s->x1;
        const int y1 = reverse ? s->y0 : s->y1;

        // if this is not a continuous segment,
        // we must add a transit command
        if (last_x != x0 || last_y != y0)
        {
            serial_draw_point(x0, y0, 0);
            int dx = x0 - last_x;
            int dy = y0 - last_y;
            m_vector_transit[0] += sqrt(dx*dx + dy*dy);
        }

        // transit to the new point
        int dx = x1 - x0;
        int dy = y1 - y0;
        int dist = sqrt(dx*dx + dy*dy);

        serial_draw_point(x1, y1, s->intensity);
        last_x = x1;
        last_y = y1;

        if (s->intensity > m_serial_bright)
            m_vector_transit[2] += dist;
        else
            m_vector_transit[1] += dist;

        // delete this segment from the list
        *min_seg = s->next;
        delete s;
    }

    // ensure that we erase our tracks
    if(this->m_serial_segments != NULL)
        fprintf(stderr, "errr?\n");
    this->m_serial_segments = NULL;
    this->m_serial_segments_tail = NULL;

    // add the "done" command to the message
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;

    size_t offset = 0;

///    if(1)
///    printf("%zu vectors: off=%u on=%u bright=%u%s\n",
///        m_serial_offset/4,
///        m_vector_transit[0],
///        m_vector_transit[1],
///        m_vector_transit[2],
///        m_serial_drop_frame ? " !" : ""
///    );

    static unsigned skip_frame;
    unsigned eagain = 0;

    if (m_serial_drop_frame || skip_frame++ % 2 != 0)
    {
        // we skipped a frame, don't skip the next one
        m_serial_drop_frame = 0;
    } else
    while (offset < m_serial_offset)
    {
        size_t wlen = m_serial_offset - offset;
///        if (wlen > 64)
///            wlen = 64;
        if (wlen > 4096)
            wlen = 4096;

///        ssize_t rc = write(m_serial_fd, m_serial_buf + offset, m_serial_offset - offset);
        ssize_t rc = write(m_serial_fd, m_serial_buf + offset, wlen);
///        if (rc <= 0)
///        {
///            eagain++;
///            if (errno == EAGAIN)
///                continue;
///            perror(m_serial);
///            close(m_serial_fd);
///            m_serial_fd = -1;
///            break;
///        }
        if (rc <= 0)
        {
            if (rc == 0) {
                eagain++;
///                Sleep(1);
                continue;
            }
            // Real error: close and bail
            perror(m_serial);
            close(m_serial_fd);
            m_serial_fd = -1;
            break;
        }

        offset += rc;
    }

///    printf("%d eagain.\n", eagain);
///    if (eagain > 20)
    if (eagain > 5)
        m_serial_drop_frame = 1;

    serial_reset();
}



void vector_device::device_start()
{
    /* Grab the settings for this session */
    m_beam_width_min = machine().options().beam_width_min();
    m_beam_width_max = machine().options().beam_width_max();
    m_beam_intensity_weight = machine().options().beam_intensity_weight();
    m_flicker = machine().options().flicker();

    m_vector_index = 0;

    /* allocate memory for tables */
    m_vector_list = make_unique_clear<point[]>(MAX_POINTS);

    /* Setup the serial output of the XY coords if configured */
    m_serial = machine().options().vector_serial();
    const float scale = machine().options().vector_scale();
    if (scale != 0.0)
    {
        // user specified a scale on the command line
        m_serial_scale_x = m_serial_scale_y = scale;
    } else {
        // use the per-axis scales
        m_serial_scale_x = machine().options().vector_scale_x();
        m_serial_scale_y = machine().options().vector_scale_y();
    }

    m_serial_segments = m_serial_segments_tail = NULL;

    m_serial_offset_x = machine().options().vector_offset_x();
    m_serial_offset_y = machine().options().vector_offset_y();
    m_serial_rotate = machine().options().vector_rotate();
    m_serial_bright = machine().options().vector_bright();
    m_serial_drop_frame = 0;
    m_serial_sort = 1;

    // allocate enough buffer space, although we should never use this much
    m_serial_buf = auto_alloc_array_clear(machine(), unsigned char, (MAX_POINTS+2) * 4);
    if (!m_serial_buf)
    {
        // todo: how to signal an error?
    }

    serial_reset();

    if (!m_serial || strcmp(m_serial,"") == 0)
    {
        fprintf(stderr, "no serial vector display configured\n");
        m_serial_fd = -1;
    } else {
        m_serial_fd = serial_open(m_serial);
        fprintf(stderr, "serial dev='%s' fd=%d\n", m_serial, m_serial_fd);
    }
}

void vector_device::set_flicker(float newval)
{
    m_flicker = newval;
}

float vector_device::get_flicker()
{
    return m_flicker;
}

void vector_device::set_beam_width_min(float newval)
{
    m_beam_width_min = newval;
}

float vector_device::get_beam_width_min()
{
    return m_beam_width_min;
}

void vector_device::set_beam_width_max(float newval)
{
    m_beam_width_max = newval;
}

float vector_device::get_beam_width_max()
{
    return m_beam_width_max;
}

void vector_device::set_beam_intensity_weight(float newval)
{
    m_beam_intensity_weight = newval;
}

float vector_device::get_beam_intensity_weight()
{
    return m_beam_intensity_weight;
}


/*
 * www.dinodini.wordpress.com/2010/04/05/normalized-tunable-sigmoid-functions/
 */
float vector_device::normalized_sigmoid(float n, float k)
{
    // valid for n and k in range of -1.0 and 1.0
    return (n - n * k) / (k - fabs(n) * 2.0f * k + 1.0f);
}


/*
 * Adds a line end point to the vertices list. The vector processor emulation
 * needs to call this.
 */
void vector_device::add_point(int x, int y, rgb_t color, int intensity)
{
    point *newpoint;

//printf("%d %d: %d,%d,%d @ %d\n", x, y, color.r(), color.b(), color.g(), intensity);

    // hack for the vectrex
    // -- convert "128,128,128" @ 255 to "255,255,255" @ 127
    if (color.r() == 128
    &&  color.b() == 128
    &&  color.g() == 128
    &&  intensity == 255)
    {
        color = rgb_t(255,255,255);
        intensity = 128;
    }

    intensity = MAX(0, MIN(255, intensity));

    m_min_intensity = intensity > 0 ? MIN(m_min_intensity, intensity) : m_min_intensity;
    m_max_intensity = intensity > 0 ? MAX(m_max_intensity, intensity) : m_max_intensity;

    if (m_flicker && (intensity > 0))
    {
        float random = (float)(machine().rand() & 255) / 255.0f; // random value between 0.0 and 1.0

        intensity -= (int)(intensity * random * m_flicker);

        intensity = MAX(0, MIN(255, intensity));
    }

    newpoint = &m_vector_list[m_vector_index];
    newpoint->x = x;
    newpoint->y = y;
    newpoint->col = color;
    newpoint->intensity = intensity;
    newpoint->status = VDIRTY; /* mark identical lines as clean later */

    m_vector_index++;
    if (m_vector_index >= MAX_POINTS)
    {
        m_vector_index--;
        logerror("*** Warning! Vector list overflow!\n");
    }
}


/*
 * Add new clipping info to the list
 */
void vector_device::add_clip(int x1, int yy1, int x2, int y2)
{
    point *newpoint;

    newpoint = &m_vector_list[m_vector_index];
    newpoint->x = x1;
    newpoint->y = yy1;
    newpoint->arg1 = x2;
    newpoint->arg2 = y2;
    newpoint->status = VCLIP;

    m_vector_index++;
    if (m_vector_index >= MAX_POINTS)
    {
        m_vector_index--;
        logerror("*** Warning! Vector list overflow!\n");
    }
}


/*
 * The vector CPU creates a new display list. We save the old display list,
 * but only once per refresh.
 */
void vector_device::clear_list(void)
{
    m_vector_index = 0;
}


UINT32 vector_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
    UINT32 flags = PRIMFLAG_ANTIALIAS(screen.machine().options().antialias() ? 1 : 0) | PRIMFLAG_BLENDMODE(BLENDMODE_ADD) | PRIMFLAG_VECTOR(1);
    const rectangle &visarea = screen.visible_area();
    float xscale = 1.0f / (65536 * visarea.width());
    float yscale = 1.0f / (65536 * visarea.height());
    float xoffs = (float)visarea.min_x;
    float yoffs = (float)visarea.min_y;
    float xratio = xscale / yscale;
    float yratio = yscale / xscale;
    xratio = (xratio < 1.0f) ? xratio : 1.0f;
    yratio = (yratio < 1.0f) ? yratio : 1.0f;

    point *curpoint;
    render_bounds clip;
    int lastx = 0;
    int lasty = 0;

    curpoint = m_vector_list.get();

    screen.container().empty();
    screen.container().add_rect(0.0f, 0.0f, 1.0f, 1.0f, rgb_t(0xff,0x00,0x00,0x00), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_VECTORBUF(1));

    clip.x0 = clip.y0 = 0.0f;
    clip.x1 = clip.y1 = 1.0f;

    for (int i = 0; i < m_vector_index; i++)
    {
        render_bounds coords;

        if (curpoint->status == VCLIP)
        {
            coords.x0 = ((float)curpoint->x - xoffs) * xscale;
            coords.y0 = ((float)curpoint->y - yoffs) * yscale;
            coords.x1 = ((float)curpoint->arg1 - xoffs) * xscale;
            coords.y1 = ((float)curpoint->arg2 - yoffs) * yscale;

            clip.x0 = (coords.x0 > 0.0f) ? coords.x0 : 0.0f;
            clip.y0 = (coords.y0 > 0.0f) ? coords.y0 : 0.0f;
            clip.x1 = (coords.x1 < 1.0f) ? coords.x1 : 1.0f;
            clip.y1 = (coords.y1 < 1.0f) ? coords.y1 : 1.0f;
        }
        else
        {
            float beam_intensity_width = m_beam_width_min;

            float intensity = (float)curpoint->intensity / 255.0f;

            // check for dynamic intensity
            if (m_min_intensity != m_max_intensity)
            {
                float intensity_weight = normalized_sigmoid(intensity, m_beam_intensity_weight);
                beam_intensity_width = (m_beam_width_max - m_beam_width_min) * intensity_weight + m_beam_width_min;
            }

            float beam_width = beam_intensity_width * (1.0f / (float)VECTOR_WIDTH_DENOM);

            coords.x0 = ((float)lastx - xoffs) * xscale;
            coords.y0 = ((float)lasty - yoffs) * yscale;
            coords.x1 = ((float)curpoint->x - xoffs) * xscale;
            coords.y1 = ((float)curpoint->y - yoffs) * yscale;

            // extend zero-length vector line (vector point) by quarter beam_width on both sides
            if (fabs(coords.x0 - coords.x1) < FLT_EPSILON &&
                fabs(coords.y0 - coords.y1) < FLT_EPSILON)
            {
                coords.x0 += xratio * beam_width * 0.25f;
                coords.y0 += yratio * beam_width * 0.25f;
                coords.x1 -= xratio * beam_width * 0.25f;
                coords.y1 -= yratio * beam_width * 0.25f;
            }

            if (curpoint->intensity != 0 && !render_clip_line(&coords, &clip))
            {
                screen.container().add_line(
                    coords.x0, coords.y0, coords.x1, coords.y1,
                    beam_width,
                    (curpoint->intensity << 24) | (curpoint->col & 0xffffff),
                    flags);

                serial_draw_line(
                    coords.x0, coords.y0,
                    coords.x1, coords.y1,
                    curpoint->intensity);
            }

            lastx = curpoint->x;
            lasty = curpoint->y;
        }

        curpoint++;
    }

    serial_send();

    return 0;
}

MAMEVECTOR64その1

はじめにMSYS2 MinGWをインストールして、Windowsで最新版のMAMEがビルドできることを確認します。

www.msys2.org

gist.github.com

つぎにLinuxのMAMEVECTOR64をWindowsでビルドします。

trmm.net

termios.hについてはこちらをベースにしました。

github.com

エラーが出たら修正を繰り返してビルドできました。

$ git clone https://github.com/veeso/termiWin
$ cp termiWin/include/termi*.h /mingw64/include/
$ git clone https://github.com/osresearch/mame/
$ cd mame
$ export MINGW64=/mingw64
$ make NOWERROR=1 SUBTARGET=vector
$ ar rcs build/mingw-gcc/bin/x64/Release/libformats.a build/mingw-gcc/obj/x64/Release/src/lib/formats/*.o
$ make NOWERROR=1 SUBTARGET=vector
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/liboptional.a $(find build/mingw-gcc/obj/x64/Release/src/devices/cpu -name "*.o")
$ make NOWERROR=1 SUBTARGET=vector
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/libbus.a build/mingw-gcc/obj/x64/Release/src/devices/bus/generic/*.o
$ make NOWERROR=1 SUBTARGET=vector
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/liboptional.a $(find build/mingw-gcc/obj/x64/Release/src/devices/machine -name "*.o")
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/liboptional.a $(find build/mingw-gcc/obj/x64/Release/src/devices/sound -name "*.o")
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/libbus.a $(find build/mingw-gcc/obj/x64/Release/src/devices/bus/vectrex -name "*.o")
$ make NOWERROR=1 SUBTARGET=vector

以下のファイルを修正しました。

scripts/build/verinfo.py(抜粋)

try:
#    fp = open(srcfile, 'rU')
    fp = open(srcfile, 'r')
except IOError:
    sys.stderr.write("Unable to open source file '%s'\n" % srcfile)
    sys.exit(1)

src/devices/cpu/m6502/m6502make.py(抜粋)

    try:
#        f = open(fname, "rU")
        f = open(fname, "r")
    except Exception:
        err = sys.exc_info()[1]
        logging.error("cannot read opcodes file %s [%s]", fname, err)
        sys.exit(1)

(省略)

    try:
#        f = open(fname, "rU")
        f = open(fname, "r")
    except Exception:
        err = sys.exc_info()[1]
        logging.error("cannot read display file %s [%s]", fname, err)
        sys.exit(1)

src/devices/cpu/m6809/m6809make.py(抜粋)

   try:
#      f = open(fname, "rU")
        f = open(fname, "r")
    except Exception:
        err = sys.exc_info()[1]
        sys.stderr.write("Cannot read opcodes file %s [%s]\n" % (fname, err))
        sys.exit(1)    

src/emu/video/vector.cpp

// license:BSD-3-Clause
// copyright-holders:Brad Oliver,Aaron Giles,Bernd Wiebelt,Allard van der Bas
/******************************************************************************
 *
 * vector.c
 *
 *        anti-alias code by Andrew Caldwell
 *        (still more to add)
 *
 * 040227 Fixed miny clip scaling which was breaking in mhavoc. AREK
 * 010903 added support for direct RGB modes MLR
 * 980611 use translucent vectors. Thanks to Peter Hirschberg
 *        and Neil Bradley for the inspiration. BW
 * 980307 added cleverer dirty handling. BW, ASG
 *        fixed antialias table .ac
 * 980221 rewrote anti-alias line draw routine
 *        added inline assembly multiply fuction for 8086 based machines
 *        beam diameter added to draw routine
 *        beam diameter is accurate in anti-alias line draw (Tcosin)
 *        flicker added .ac
 * 980203 moved LBO's routines for drawing into a buffer of vertices
 *        from avgdvg.c to this location. Scaling is now initialized
 *        by calling vector_init(...). BW
 * 980202 moved out of msdos.c ASG
 * 980124 added anti-alias line draw routine
 *        modified avgdvg.c and sega.c to support new line draw routine
 *        added two new tables Tinten and Tmerge (for 256 color support)
 *        added find_color routine to build above tables .ac
 *
 **************************************************************************** */

#include "emu.h"
#include "emuopts.h"
#include "rendutil.h"
#include "vector.h"

// Serial port related includes
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>

#include <inttypes.h>
#include <sys/time.h>
 

#define FLT_EPSILON 1E-5

#define VECTOR_WIDTH_DENOM 512

#define MAX_POINTS 10000

#define VECTOR_SERIAL_MAX 4095

#define VECTOR_TEAM \
   "-* Vector Heads *-\n" \
   "Brad Oliver\n" \
   "Aaron Giles\n" \
   "Bernd Wiebelt\n" \
   "Allard van der Bas\n" \
   "Al Kossow (VECSIM)\n" \
   "Hedley Rainnie (VECSIM)\n" \
   "Eric Smith (VECSIM)\n" \
   "Neil Bradley (technical advice)\n" \
   "Andrew Caldwell (anti-aliasing)\n" \
   "- *** -\n"

///-----
#ifdef __cplusplus > 201711L
  #define TERMIWIN_MAYBE_UNUSED [[maybe_unused]]
#else
  #ifdef __GNUC__
    #define TERMIWIN_MAYBE_UNUSED __attribute__((unused))
  #else
    #define TERMIWIN_MAYBE_UNUSED
  #endif
#endif

#include <fcntl.h>
#include <stdlib.h>

typedef struct COM {
  HANDLE hComm;
  int fd; //Actually it's completely useless
  char port[128];
} COM;

DCB SerialParams = { 0 }; //Initializing DCB structure
struct COM com;
COMMTIMEOUTS timeouts = { 0 }; //Initializing COMMTIMEOUTS structure

//LOCAL functions

//nbyte 0->7

int getByte(tcflag_t flag, int nbyte, int nibble) {

  int byte;
  if (nibble == 1)
    byte = (flag >> (8 * (nbyte)) & 0x0f);
  else
    byte = (flag >> (8 * (nbyte)) & 0xf0);
  return byte;
}

//INPUT FUNCTIONS

enum{
  i_IXOFF = 0x01,
  i_IXON = 0x02,
  i_IXOFF_IXON = 0x03,
  i_PARMRK = 0x04,
  i_PARMRK_IXOFF = 0x05,
  i_PARMRK_IXON = 0x06,
  i_PARMRK_IXON_IXOFF = 0x07
};

int getIXOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);

  return byte;
}

//LOCALOPT FUNCTIONS

enum{
  l_NOECHO = 0x00,
  l_ECHO = 0x01,
  l_ECHO_ECHOE = 0x03,
  l_ECHO_ECHOK = 0x05,
  l_ECHO_ECHONL = 0x09,
  l_ECHO_ECHOE_ECHOK = 0x07,
  l_ECHO_ECHOE_ECHONL = 0x0b,
  l_ECHO_ECHOE_ECHOK_ECHONL = 0x0f,
  l_ECHO_ECHOK_ECHONL = 0x0d,
  l_ECHOE = 0x02,
  l_ECHOE_ECHOK = 0x06,
  l_ECHOE_ECHONL = 0x0a,
  l_ECHOE_ECHOK_ECHONL = 0x0e,
  l_ECHOK = 0x04,
  l_ECHOK_ECHONL = 0x0c,
  l_ECHONL = 0x08
};

int getEchoOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);
  return byte;
}

enum{
  l_ICANON = 0x10,
  l_ICANON_ISIG = 0x50,
  l_ICANON_IEXTEN = 0x30,
  l_ICANON_NOFLSH = 0x90,
  l_ICANON_ISIG_IEXTEN = 0x70,
  l_ICANON_ISIG_NOFLSH = 0xd0,
  l_ICANON_IEXTEN_NOFLSH = 0xb0,
  l_ICANON_ISIG_IEXTEN_NOFLSH = 0xf0,
  l_ISIG = 0x40,
  l_ISIG_IEXTEN = 0x60,
  l_ISIG_NOFLSH = 0xc0,
  l_ISIG_IEXTEN_NOFLSH = 0xe0,
  l_IEXTEN = 0x20,
  l_IEXTEN_NOFLSH = 0xa0,
  l_NOFLSH = 0x80,
};

int getLocalOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 0);
  return byte;
}

enum{
  l_TOSTOP = 0x01
};

int getToStop(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);
  return byte;
}

//CONTROLOPT FUNCTIONS

int getCharSet(tcflag_t flag) {

  //FLAG IS MADE UP OF 8 BYTES, A FLAG IS MADE UP OF A NIBBLE -> 4 BITS, WE NEED TO EXTRACT THE SECOND NIBBLE (1st) FROM THE FIFTH BYTE (6th).
  int byte = getByte(flag, 1, 1);

  switch (byte) {

  case 0X0:
    return CS5;
    break;

  case 0X4:
    return CS6;
    break;

  case 0X8:
    return CS7;
    break;

  case 0Xc:
    return CS8;
    break;

  default:
    return CS8;
    break;
  }
}

enum{
  c_ALL_ENABLED = 0xd0,
  c_PAREVEN_CSTOPB = 0x50,
  c_PAREVEN_NOCSTOPB = 0x40,
  c_PARODD_NOCSTOPB = 0xc0,
  c_NOPARENB_CSTOPB = 0x10,
  c_ALL_DISABLED = 0x00,
};

int getControlOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 0);
  return byte;
}

//LIBFUNCTIONS

int tcgetattr(int fd, struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  if (fd != com.fd) return -1;
  int TERMIWIN_MAYBE_UNUSED ret = 0;

  ret = GetCommState(com.hComm, &SerialParams);

  return 0;
}

int tcsetattr(int fd, int TERMIWIN_MAYBE_UNUSED optional_actions, const struct termios* termios_p) {

  if (fd != com.fd) return -1;
  int ret = 0;

  //Store flags into local variables
  tcflag_t iflag = termios_p->c_iflag;
  tcflag_t lflag = termios_p->c_lflag;
  tcflag_t cflag = termios_p->c_cflag;
  tcflag_t TERMIWIN_MAYBE_UNUSED oflag = termios_p->c_oflag;

  //iflag

  int IX = getIXOptions(iflag);

  if ((IX == i_IXOFF_IXON) || (IX == i_PARMRK_IXON_IXOFF)) {

    SerialParams.fOutX = TRUE;
    SerialParams.fInX = TRUE;
    SerialParams.fTXContinueOnXoff = TRUE;
  }

  //lflag
  int TERMIWIN_MAYBE_UNUSED EchoOpt = getEchoOptions(lflag);
  int TERMIWIN_MAYBE_UNUSED l_opt = getLocalOptions(lflag);
  int TERMIWIN_MAYBE_UNUSED tostop = getToStop(lflag);

  //Missing parameters...

  //cflags

  int CharSet = getCharSet(cflag);
  int c_opt = getControlOptions(cflag);

  switch (CharSet) {

  case CS5:
    SerialParams.ByteSize = 5;
    break;

  case CS6:
    SerialParams.ByteSize = 6;
    break;

  case CS7:
    SerialParams.ByteSize = 7;
    break;

  case CS8:
    SerialParams.ByteSize = 8;
    break;
  }

  switch (c_opt) {

  case c_ALL_ENABLED:
    SerialParams.Parity = ODDPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;

  case c_ALL_DISABLED:
    SerialParams.Parity = NOPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_PAREVEN_CSTOPB:
    SerialParams.Parity = EVENPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;

  case c_PAREVEN_NOCSTOPB:
    SerialParams.Parity = EVENPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_PARODD_NOCSTOPB:
    SerialParams.Parity = ODDPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_NOPARENB_CSTOPB:
    SerialParams.Parity = NOPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;
  }

  //aflags

  /*
  int OP;
  if(oflag == OPOST)
  else ...
  */
  //Missing parameters...

  //special characters

  if (termios_p->c_cc[VEOF] != 0) SerialParams.EofChar = (char)termios_p->c_cc[VEOF];
  if (termios_p->c_cc[VINTR] != 0) SerialParams.EvtChar = (char)termios_p->c_cc[VINTR];

  if (termios_p->c_cc[VMIN] == 1) { //Blocking

    timeouts.ReadIntervalTimeout = 0;         // in milliseconds
    timeouts.ReadTotalTimeoutConstant = 0;    // in milliseconds
    timeouts.ReadTotalTimeoutMultiplier = 0;  // in milliseconds
    timeouts.WriteTotalTimeoutConstant = 0;   // in milliseconds
    timeouts.WriteTotalTimeoutMultiplier = 0; // in milliseconds

  } else { //Non blocking

    timeouts.ReadIntervalTimeout = termios_p->c_cc[VTIME] * 100;         // in milliseconds
    timeouts.ReadTotalTimeoutConstant = termios_p->c_cc[VTIME] * 100;    // in milliseconds
    timeouts.ReadTotalTimeoutMultiplier = termios_p->c_cc[VTIME] * 100;  // in milliseconds
    timeouts.WriteTotalTimeoutConstant = termios_p->c_cc[VTIME] * 100;   // in milliseconds
    timeouts.WriteTotalTimeoutMultiplier = termios_p->c_cc[VTIME] * 100; // in milliseconds
  }

  SetCommTimeouts(com.hComm, &timeouts);

  //EOF

  ret = SetCommState(com.hComm, &SerialParams);
  if (ret != 0)
    return 0;
  else
    return -1;
}

int tcsendbreak(int fd, int TERMIWIN_MAYBE_UNUSED duration) {

  if (fd != com.fd) return -1;

  int ret = 0;
  ret = TransmitCommChar(com.hComm, '\x00');
  if (ret != 0)
    return 0;
  else
    return -1;
}

int tcdrain(int fd) {

  if (fd != com.fd) return -1;
  return FlushFileBuffers(com.hComm);
}

int tcflush(int fd, int queue_selector) {

  if (fd != com.fd) return -1;
  int rc = 0;

  switch (queue_selector) {

  case TCIFLUSH:
    rc = PurgeComm(com.hComm, PURGE_RXCLEAR);
    break;

  case TCOFLUSH:
    rc = PurgeComm(com.hComm, PURGE_TXCLEAR);
    break;

  case TCIOFLUSH:
    rc = PurgeComm(com.hComm, PURGE_RXCLEAR);
    rc *= PurgeComm(com.hComm, PURGE_TXCLEAR);
    break;

  default:
    rc = 0;
    break;
  }

  if (rc != 0)
    return 0;
  else
    return -1;
}

int tcflow(int fd, int action) {

  if (fd != com.fd) return -1;
  int rc = 0;

  switch (action) {

  case TCOOFF:
    rc = PurgeComm(com.hComm, PURGE_TXABORT);
    break;

  case TCOON:
    rc = ClearCommBreak(com.hComm);
    break;

  case TCIOFF:
    rc = PurgeComm(com.hComm, PURGE_RXABORT);
    break;

  case TCION:
    rc = ClearCommBreak(com.hComm);
    break;

  default:
    rc = 0;
    break;
  }

  if (rc != 0)
    return 0;
  else
    return -1;
}

void cfmakeraw(struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  SerialParams.ByteSize = 8;
  SerialParams.StopBits = ONESTOPBIT;
  SerialParams.Parity = NOPARITY;
}

speed_t cfgetispeed(const struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  return SerialParams.BaudRate;
}

speed_t cfgetospeed(const struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  return SerialParams.BaudRate;
}

int cfsetispeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

int cfsetospeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

int cfsetspeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

ssize_t read_serial(int fd, void* buffer, size_t count) {

  if (fd != com.fd) return -1;
///  int rc = 0;
  DWORD rc = 0;
  int ret;

  ret = ReadFile(com.hComm, buffer, count, &rc, NULL);

  if (ret == 0)
    return -1;
  else
    return rc;
}

ssize_t write_serial(int fd, const void* buffer, size_t count) {

  if (fd != com.fd) return -1;
///  int rc = 0;
  DWORD rc = 0;
  int ret;

  ret = WriteFile(com.hComm, buffer, count, &rc, NULL);

  if (ret == 0)
    return -1;
  else
    return rc;
}

int open_serial(const char* portname, int opt) {

  if (strlen(portname) < 4) return -1;

  // Set to zero
  memset(com.port, 0x00, 128);

  //COMxx
  size_t portSize = 0;
  if (strlen(portname) > 4) {
    portSize = sizeof(char) * strlen("\\\\.\\COM10") + 1;
#ifdef _MSC_VER
    strncat_s(com.port, portSize, "\\\\.\\", strlen("\\\\.\\"));
#else
    strncat(com.port, "\\\\.\\", strlen("\\\\.\\"));
#endif
  }
  //COMx
  else {
    portSize = sizeof(char) * 5;
  }

#ifdef _MSC_VER
  strncat_s(com.port, portSize, portname, 4);
#else
  strncat(com.port, portname, 4);
#endif
  com.port[portSize] = 0x00;

  switch (opt) {

  case O_RDWR:
    com.hComm = CreateFile(com.port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;

  case O_RDONLY:
    com.hComm = CreateFile(com.port, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;

  case O_WRONLY:
    com.hComm = CreateFile(com.port, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;
  }

  if (com.hComm == INVALID_HANDLE_VALUE) {
    return -1;
  }
  com.fd = atoi(portname + 3); // COMx and COMxx
  SerialParams.DCBlength = sizeof(SerialParams);
  return com.fd;
}

int close_serial(int TERMIWIN_MAYBE_UNUSED fd) {

  int ret = CloseHandle(com.hComm);
  if (ret != 0)
    return 0;
  else
    return -1;
}

int select_serial(int TERMIWIN_MAYBE_UNUSED nfds, fd_set* readfds, fd_set* TERMIWIN_MAYBE_UNUSED writefds, fd_set* TERMIWIN_MAYBE_UNUSED exceptfds, struct timeval* TERMIWIN_MAYBE_UNUSED timeout) {

  SetCommMask(com.hComm, EV_RXCHAR);
  DWORD dwEventMask;
  if (WaitCommEvent(com.hComm, &dwEventMask, NULL) == 0) {
    return -1; // Return -1 if failed
  }
  if (dwEventMask == EV_RXCHAR) {
    return com.fd;
  } else {
    if (readfds) {
      // Clear file descriptor if event is not RXCHAR
      FD_CLR(com.fd, readfds);
    }
  }
  // NOTE: write event not detectable!
  // NOTE: no timeout
  return 0; // No data
}

//Returns hComm from the COM structure
HANDLE getHandle() {
  return com.hComm;
}
///-----

#define VCLEAN  0
#define VDIRTY  1
#define VCLIP   2

// device type definition
const device_type VECTOR = &device_creator<vector_device>;

vector_device::vector_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source)
    : device_t(mconfig, type, name, tag, owner, clock, shortname, source),
        device_video_interface(mconfig, *this),
        m_vector_list(nullptr),
        m_min_intensity(255),
        m_max_intensity(0)
{
}

vector_device::vector_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
    : device_t(mconfig, VECTOR, "VECTOR", tag, owner, clock, "vector_device", __FILE__),
        device_video_interface(mconfig, *this),
        m_vector_list(nullptr),
        m_min_intensity(255),
        m_max_intensity(0)
{
}

float vector_device::m_flicker = 0.0f;
float vector_device::m_beam_width_min = 0.0f;
float vector_device::m_beam_width_max = 0.0f;
float vector_device::m_beam_intensity_weight = 0.0f;
int vector_device::m_vector_index;

struct serial_segment_t {
    struct serial_segment_t * next;
    int intensity;
    int x0;
    int y0;
    int x1;
    int y1;

    serial_segment_t(
        int x0,
        int y0,
        int x1,
        int y1,
        int intensity
    ) :
        next(NULL),
        intensity(intensity),
        x0(x0),
        y0(y0),
        x1(x1),
        y1(y1)
    {
    }
};

int
serial_open(
        const char * const dev
)
{
///        const int fd = open(dev, O_RDWR | O_NONBLOCK | O_NOCTTY, 0666);
        const int fd = open(dev, O_RDWR, 0666);
        if (fd < 0)
                return -1;

        // Disable modem control signals
        struct termios attr;
        tcgetattr(fd, &attr);
        attr.c_cflag |= CLOCAL | CREAD;
        attr.c_oflag &= ~OPOST;
        tcsetattr(fd, TCSANOW, &attr);

        return fd;
}

void vector_device::serial_draw_point(
    unsigned x,
    unsigned y,
    int intensity
)
{
    // make sure that we are in range; should always be
    // due to clipping on the window, but just in case
    if (x < 0) x = 0;
    if (y < 0) y = 0;

    if (x > VECTOR_SERIAL_MAX) x = VECTOR_SERIAL_MAX;
    if (y > VECTOR_SERIAL_MAX) y = VECTOR_SERIAL_MAX;

    // always flip the Y, since the vectorscope measures
    // 0,0 at the bottom left corner, but this coord uses
    // the top left corner.
    y = VECTOR_SERIAL_MAX - y;

    unsigned bright;
    if (intensity > m_serial_bright)
        bright = 63;
    else
    if (intensity <= 0)
        bright = 0;
    else
        bright = (intensity * 64) / 256;

    if (bright > 63)
        bright = 63;

    if (m_serial_rotate == 1)
    {
        // +90
        unsigned tmp = x;
        x = VECTOR_SERIAL_MAX - y;
        y = tmp;
    } else
    if (m_serial_rotate == 2)
    {
        // +180
        x = VECTOR_SERIAL_MAX - x;
        y = VECTOR_SERIAL_MAX - y;
    } else
    if (m_serial_rotate == 3)
    {
        // -90
        unsigned t = x;
        x = y;
        y = VECTOR_SERIAL_MAX - t;
    }

    uint32_t cmd = 0
        | (2 << 30)
        | (bright & 0x3F) << 24
        | (x & 0xFFF) << 12
        | (y & 0xFFF) <<  0
        ;

    //printf("%08x %8d %8d %3d\n", cmd, x, y, intensity);

    m_serial_buf[m_serial_offset++] = cmd >> 24;
    m_serial_buf[m_serial_offset++] = cmd >> 16;
    m_serial_buf[m_serial_offset++] = cmd >>  8;
    m_serial_buf[m_serial_offset++] = cmd >>  0;

    // todo: check for overflow;
    // should always have enough points
}


// This will only be called with non-zero intensity lines.
// we keep a linked list of the vectors and sort them with
// a greedy insertion sort.
void vector_device::serial_draw_line(
    float xf0,
    float yf0,
    float xf1,
    float yf1,
    int intensity
)
{
    if (m_serial_fd < 0)
        return;

    // scale and shift each of the axes.
    const int x0 = (xf0 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_x + m_serial_offset_x;
    const int y0 = (yf0 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_y + m_serial_offset_y;
    const int x1 = (xf1 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_x + m_serial_offset_x;
    const int y1 = (yf1 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_y + m_serial_offset_y;

    serial_segment_t * const new_segment
        = new serial_segment_t(x0, y0, x1, y1, intensity);

    if (this->m_serial_segments_tail)
        this->m_serial_segments_tail->next = new_segment;
    else
        this->m_serial_segments = new_segment;

    this->m_serial_segments_tail = new_segment;
}


void vector_device::serial_reset()
{
    m_serial_offset = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;

    m_vector_transit[0] = 0;
    m_vector_transit[1] = 0;
    m_vector_transit[2] = 0;
}


void vector_device::serial_send()
{
    if (m_serial_fd < 0)
        return;

    int last_x = -1;
    int last_y = -1;

    // find the next closest point to the last one.
    // greedy sorting algorithm reduces beam transit time
    // fairly significantly. doesn't matter for the
    // vectorscope, but makes a big difference for Vectrex
    // and other slower displays.
    while(this->m_serial_segments)
    {
        int reverse = 0;
        int min = 1e6;
        serial_segment_t ** min_seg
            = &this->m_serial_segments;

        if (m_serial_sort)
        for(serial_segment_t ** s = min_seg ; *s ; s = &(*s)->next)
        {
            int dx0 = (*s)->x0 - last_x;
            int dy0 = (*s)->y0 - last_y;
            int dx1 = (*s)->x1 - last_x;
            int dy1 = (*s)->y1 - last_y;
            int d0 = sqrt(dx0*dx0 + dy0*dy0);
            int d1 = sqrt(dx1*dx1 + dy1*dy1);

            if(d0 < min)
            {
                min_seg = s;
                min = d0;
                reverse = 0;
            }

            if (d1 < min)
            {
                min_seg = s;
                min = d1;
                reverse = 1;
            }

            // if we have hit two identical points,
            // then stop the search here.
            if (min == 0)
                break;
        }

        serial_segment_t * const s = *min_seg;
        if (!s)
            break;
    
        const int x0 = reverse ? s->x1 : s->x0;
        const int y0 = reverse ? s->y1 : s->y0;
        const int x1 = reverse ? s->x0 : s->x1;
        const int y1 = reverse ? s->y0 : s->y1;

        // if this is not a continuous segment,
        // we must add a transit command
        if (last_x != x0 || last_y != y0)
        {
            serial_draw_point(x0, y0, 0);
            int dx = x0 - last_x;
            int dy = y0 - last_y;
            m_vector_transit[0] += sqrt(dx*dx + dy*dy);
        }

        // transit to the new point
        int dx = x1 - x0;
        int dy = y1 - y0;
        int dist = sqrt(dx*dx + dy*dy);

        serial_draw_point(x1, y1, s->intensity);
        last_x = x1;
        last_y = y1;

        if (s->intensity > m_serial_bright)
            m_vector_transit[2] += dist;
        else
            m_vector_transit[1] += dist;

        // delete this segment from the list
        *min_seg = s->next;
        delete s;
    }

    // ensure that we erase our tracks
    if(this->m_serial_segments != NULL)
        fprintf(stderr, "errr?\n");
    this->m_serial_segments = NULL;
    this->m_serial_segments_tail = NULL;

    // add the "done" command to the message
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;

    size_t offset = 0;

    if(1)
    printf("%zu vectors: off=%u on=%u bright=%u%s\n",
        m_serial_offset/4,
        m_vector_transit[0],
        m_vector_transit[1],
        m_vector_transit[2],
        m_serial_drop_frame ? " !" : ""
    );

    static unsigned skip_frame;
    unsigned eagain = 0;

    if (m_serial_drop_frame || skip_frame++ % 2 != 0)
    {
        // we skipped a frame, don't skip the next one
        m_serial_drop_frame = 0;
    } else
    while (offset < m_serial_offset)
    {
        size_t wlen = m_serial_offset - offset;
///        if (wlen > 64)
///            wlen = 64;
        if (wlen > 4096)
            wlen = 4096;

        ssize_t rc = write(m_serial_fd, m_serial_buf + offset, m_serial_offset - offset);
        if (rc <= 0)
        {
            eagain++;
            if (errno == EAGAIN)
                continue;
            perror(m_serial);
            close(m_serial_fd);
            m_serial_fd = -1;
            break;
        }

        offset += rc;
    }

    printf("%d eagain.\n", eagain);
    if (eagain > 20)
        m_serial_drop_frame = 1;

    serial_reset();
}



void vector_device::device_start()
{
    /* Grab the settings for this session */
    m_beam_width_min = machine().options().beam_width_min();
    m_beam_width_max = machine().options().beam_width_max();
    m_beam_intensity_weight = machine().options().beam_intensity_weight();
    m_flicker = machine().options().flicker();

    m_vector_index = 0;

    /* allocate memory for tables */
    m_vector_list = make_unique_clear<point[]>(MAX_POINTS);

    /* Setup the serial output of the XY coords if configured */
    m_serial = machine().options().vector_serial();
    const float scale = machine().options().vector_scale();
    if (scale != 0.0)
    {
        // user specified a scale on the command line
        m_serial_scale_x = m_serial_scale_y = scale;
    } else {
        // use the per-axis scales
        m_serial_scale_x = machine().options().vector_scale_x();
        m_serial_scale_y = machine().options().vector_scale_y();
    }

    m_serial_segments = m_serial_segments_tail = NULL;

    m_serial_offset_x = machine().options().vector_offset_x();
    m_serial_offset_y = machine().options().vector_offset_y();
    m_serial_rotate = machine().options().vector_rotate();
    m_serial_bright = machine().options().vector_bright();
    m_serial_drop_frame = 0;
    m_serial_sort = 1;

    // allocate enough buffer space, although we should never use this much
    m_serial_buf = auto_alloc_array_clear(machine(), unsigned char, (MAX_POINTS+2) * 4);
    if (!m_serial_buf)
    {
        // todo: how to signal an error?
    }

    serial_reset();

    if (!m_serial || strcmp(m_serial,"") == 0)
    {
        fprintf(stderr, "no serial vector display configured\n");
        m_serial_fd = -1;
    } else {
        m_serial_fd = serial_open(m_serial);
        fprintf(stderr, "serial dev='%s' fd=%d\n", m_serial, m_serial_fd);
    }
}

void vector_device::set_flicker(float newval)
{
    m_flicker = newval;
}

float vector_device::get_flicker()
{
    return m_flicker;
}

void vector_device::set_beam_width_min(float newval)
{
    m_beam_width_min = newval;
}

float vector_device::get_beam_width_min()
{
    return m_beam_width_min;
}

void vector_device::set_beam_width_max(float newval)
{
    m_beam_width_max = newval;
}

float vector_device::get_beam_width_max()
{
    return m_beam_width_max;
}

void vector_device::set_beam_intensity_weight(float newval)
{
    m_beam_intensity_weight = newval;
}

float vector_device::get_beam_intensity_weight()
{
    return m_beam_intensity_weight;
}


/*
 * www.dinodini.wordpress.com/2010/04/05/normalized-tunable-sigmoid-functions/
 */
float vector_device::normalized_sigmoid(float n, float k)
{
    // valid for n and k in range of -1.0 and 1.0
    return (n - n * k) / (k - fabs(n) * 2.0f * k + 1.0f);
}


/*
 * Adds a line end point to the vertices list. The vector processor emulation
 * needs to call this.
 */
void vector_device::add_point(int x, int y, rgb_t color, int intensity)
{
    point *newpoint;

//printf("%d %d: %d,%d,%d @ %d\n", x, y, color.r(), color.b(), color.g(), intensity);

    // hack for the vectrex
    // -- convert "128,128,128" @ 255 to "255,255,255" @ 127
    if (color.r() == 128
    &&  color.b() == 128
    &&  color.g() == 128
    &&  intensity == 255)
    {
        color = rgb_t(255,255,255);
        intensity = 128;
    }

    intensity = MAX(0, MIN(255, intensity));

    m_min_intensity = intensity > 0 ? MIN(m_min_intensity, intensity) : m_min_intensity;
    m_max_intensity = intensity > 0 ? MAX(m_max_intensity, intensity) : m_max_intensity;

    if (m_flicker && (intensity > 0))
    {
        float random = (float)(machine().rand() & 255) / 255.0f; // random value between 0.0 and 1.0

        intensity -= (int)(intensity * random * m_flicker);

        intensity = MAX(0, MIN(255, intensity));
    }

    newpoint = &m_vector_list[m_vector_index];
    newpoint->x = x;
    newpoint->y = y;
    newpoint->col = color;
    newpoint->intensity = intensity;
    newpoint->status = VDIRTY; /* mark identical lines as clean later */

    m_vector_index++;
    if (m_vector_index >= MAX_POINTS)
    {
        m_vector_index--;
        logerror("*** Warning! Vector list overflow!\n");
    }
}


/*
 * Add new clipping info to the list
 */
void vector_device::add_clip(int x1, int yy1, int x2, int y2)
{
    point *newpoint;

    newpoint = &m_vector_list[m_vector_index];
    newpoint->x = x1;
    newpoint->y = yy1;
    newpoint->arg1 = x2;
    newpoint->arg2 = y2;
    newpoint->status = VCLIP;

    m_vector_index++;
    if (m_vector_index >= MAX_POINTS)
    {
        m_vector_index--;
        logerror("*** Warning! Vector list overflow!\n");
    }
}


/*
 * The vector CPU creates a new display list. We save the old display list,
 * but only once per refresh.
 */
void vector_device::clear_list(void)
{
    m_vector_index = 0;
}


UINT32 vector_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
    UINT32 flags = PRIMFLAG_ANTIALIAS(screen.machine().options().antialias() ? 1 : 0) | PRIMFLAG_BLENDMODE(BLENDMODE_ADD) | PRIMFLAG_VECTOR(1);
    const rectangle &visarea = screen.visible_area();
    float xscale = 1.0f / (65536 * visarea.width());
    float yscale = 1.0f / (65536 * visarea.height());
    float xoffs = (float)visarea.min_x;
    float yoffs = (float)visarea.min_y;
    float xratio = xscale / yscale;
    float yratio = yscale / xscale;
    xratio = (xratio < 1.0f) ? xratio : 1.0f;
    yratio = (yratio < 1.0f) ? yratio : 1.0f;

    point *curpoint;
    render_bounds clip;
    int lastx = 0;
    int lasty = 0;

    curpoint = m_vector_list.get();

    screen.container().empty();
    screen.container().add_rect(0.0f, 0.0f, 1.0f, 1.0f, rgb_t(0xff,0x00,0x00,0x00), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_VECTORBUF(1));

    clip.x0 = clip.y0 = 0.0f;
    clip.x1 = clip.y1 = 1.0f;

    for (int i = 0; i < m_vector_index; i++)
    {
        render_bounds coords;

        if (curpoint->status == VCLIP)
        {
            coords.x0 = ((float)curpoint->x - xoffs) * xscale;
            coords.y0 = ((float)curpoint->y - yoffs) * yscale;
            coords.x1 = ((float)curpoint->arg1 - xoffs) * xscale;
            coords.y1 = ((float)curpoint->arg2 - yoffs) * yscale;

            clip.x0 = (coords.x0 > 0.0f) ? coords.x0 : 0.0f;
            clip.y0 = (coords.y0 > 0.0f) ? coords.y0 : 0.0f;
            clip.x1 = (coords.x1 < 1.0f) ? coords.x1 : 1.0f;
            clip.y1 = (coords.y1 < 1.0f) ? coords.y1 : 1.0f;
        }
        else
        {
            float beam_intensity_width = m_beam_width_min;

            float intensity = (float)curpoint->intensity / 255.0f;

            // check for dynamic intensity
            if (m_min_intensity != m_max_intensity)
            {
                float intensity_weight = normalized_sigmoid(intensity, m_beam_intensity_weight);
                beam_intensity_width = (m_beam_width_max - m_beam_width_min) * intensity_weight + m_beam_width_min;
            }

            float beam_width = beam_intensity_width * (1.0f / (float)VECTOR_WIDTH_DENOM);

            coords.x0 = ((float)lastx - xoffs) * xscale;
            coords.y0 = ((float)lasty - yoffs) * yscale;
            coords.x1 = ((float)curpoint->x - xoffs) * xscale;
            coords.y1 = ((float)curpoint->y - yoffs) * yscale;

            // extend zero-length vector line (vector point) by quarter beam_width on both sides
            if (fabs(coords.x0 - coords.x1) < FLT_EPSILON &&
                fabs(coords.y0 - coords.y1) < FLT_EPSILON)
            {
                coords.x0 += xratio * beam_width * 0.25f;
                coords.y0 += yratio * beam_width * 0.25f;
                coords.x1 -= xratio * beam_width * 0.25f;
                coords.y1 -= yratio * beam_width * 0.25f;
            }

            if (curpoint->intensity != 0 && !render_clip_line(&coords, &clip))
            {
                screen.container().add_line(
                    coords.x0, coords.y0, coords.x1, coords.y1,
                    beam_width,
                    (curpoint->intensity << 24) | (curpoint->col & 0xffffff),
                    flags);

                serial_draw_line(
                    coords.x0, coords.y0,
                    coords.x1, coords.y1,
                    curpoint->intensity);
            }

            lastx = curpoint->x;
            lasty = curpoint->y;
        }

        curpoint++;
    }

    serial_send();

    return 0;
}

アドベンチャーゲーム

ZORK1を攻略しました。 tms9918.hatenablog.com

ソースコードもあります。 github.com

Vezzaを使ってZORK1やColossal Cave AdventureをMSXで遊ぶことが出来ます。CP/M版ZORK1.DATはZ3ファイルのようです。 gitlab.com

zmachine-multilingualを使って、CP/M版ZORK1を日本語で遊ぶことが出来ます。 github.com

$ cat ZORK1.DAT > zork1.z3
$ sbcl --script run-zork.lisp
Loaded: translations-ja.lisp
Loaded user file: translations-ja.lisp
Language: Japanese (日本語)
Bilingual mode: enabled
Translations loaded: 169
Auto-save: enabled
Loaded Z-machine version 3 story file
  Dynamic memory: 0 - 2E53
  High memory: 4E37 - 14C00
  Initial PC: 4F05

ZORK I: The Great Underground Empire
Copyright (c) 1981, 1982, 1983 Infocom, Inc. All rights reserved.
ZORK is a registered trademark of Infocom, Inc.
Revision 88 / Serial number 840726

West of House
You are standing in an open field west of a white house, with a boarded front door.
There is a small mailbox here.

家の西側
あなたは白い家の西側の開けた野原に立っています。正面のドアは板で塞がれています。
ここに小さな郵便受けがあります。
>

Colossal Cave Adventureを攻略しました。 tms9918.hatenablog.com

zmachine-multilingualを使ってColossal Cave Adventureも日本語で遊ぶことが出来ます。 microheaven.com

$ sbcl --script run-advent.lisp
Loaded: translations-ja.lisp
Loaded user file: translations-ja.lisp
Language: Japanese (日本語)
Bilingual mode: enabled
Translations loaded: 169
Auto-save: enabled
DeepL API configured.
Loaded Z-machine version 3 story file
  Dynamic memory: 0 - 35AA
  High memory: 4E7E - 13400
  Initial PC: 4E7F

Welcome to Adventure!
(Please type HELP for instructions and information.)

ADVENTURE
The Interactive Original
By Will Crowther (1976) and Don Woods (1977)
Reconstructed in three steps by:
Donald Ekman, David M. Baggett (1993) and Graham Nelson (1994)
PunyInform version: Fredrik Ramsberg (2024)
[In memoriam Stephen Bishop (1820?-1857): GN]

Release 9 / Serial number 260512 / Inform v6.45 PunyInform v6.6

At End Of Road
You are standing at the end of a road before a small brick building. Around you is a forest. A small stream flows out of the building and down a gully.

アドベンチャーへようこそ!
(操作方法や情報については、「HELP」と入力してください。)
アドベンチャー
インタラクティブ・オリジナル
ウィル・クロウザー(1976年)およびドン・ウッズ(1977年)作
3段階に分けて再構築:
ドナルド・エクマン、デビッド・M・バゲット(1993年)、グラハム・ネルソン (1994年)
PunyInform版:フレドリック・ラムスバーグ(2024年)
[スティーブン・ビショップ(1820?–1857)を追悼して:GN]
リリース 9 / シリアル番号 260512 / Inform v6.45 PunyInform v6.6
道の果てで
あなたは道の突き当たり、小さなレンガ造りの建物の前に立っています。周囲は森に囲まれています。建物から小さな小川が流れ出し、谷間へと下っています。
>

Fortran版をPDP-11で遊ぶことが出来ます。 trmm.net

Fortran版のADVENT.DATをPythonで遊ぶことが出来ます。 github.com

Fortran版の一番古いものはこちらのようです。 github.com

Fortran版のソースコードを読むのもいいですし、ZIL版のソースコードを読むのもいいですね。 zilf.io

SEGA GENESIS COLLECTIONを解析してみた!

米国版PS2ソフトになります。 www.amazon.com

ここらへんを参考にして解析してみる。

github.com

例えばTac/Scanであれば、TACSCAN.SRを展開してTACSCAN.ROMを生成します。

$ python xsr.py TACSCAN.SR
files: 22
./ic_TACSCAN.IA 0 11856
./ic_TACSCAN.PNG 12288 31859
./TACSCAN.ROM 45056 45056
./TacScanS18.wav 90112 24750
./TacScanS1C.wav 116736 45830
./TacScanS20.wav 163840 16766
./TacScanS28.wav 182272 24278
./TacScanS2C.wav 206848 45830
./TacScanS31.wav 253952 64206
./TacScanS32.wav 319488 67772
./TacScanS33.wav 389120 73858
./TacScanS34.wav 464896 69918
./TacScanS35.wav 536576 71846
./TacScanS36.wav 610304 74070
./TacScanS37.wav 686080 74970
./TacScanS37A.wav 761856 61740
./TacScanS48.wav 825344 7874
./TacScanS50.wav 833536 5036
./TacScanS51.wav 839680 2528
./TacScanS54.wav 843776 83748
./TacScanS60.wav 927744 17030
./TacScanS6C.wav 946176 37510

CRCを調べて切り出します。 https://github.com/mamedev/mame/blob/master/src/mame/sega/segag80v.cpp

ROM_START( tacscan )
    ROM_REGION( 0xc000, "maincpu", 0 )
    ROM_LOAD( "1711a.cpu-u25",  0x0000, 0x0800, CRC(0da13158) SHA1(256c5441a4841441501c9b7bcf09e0e99e8dd671) )
    ROM_LOAD( "1670c.prom-u1",  0x0800, 0x0800, CRC(98de6fd5) SHA1(f22c215d7558e00366fec5092abb51c670468f8c) )
    ROM_LOAD( "1671a.prom-u2",  0x1000, 0x0800, CRC(dc400074) SHA1(70093ef56e0784173a06da1ac781bb9d8c4e7fc5) )
    ROM_LOAD( "1672a.prom-u3",  0x1800, 0x0800, CRC(2caf6f7e) SHA1(200119260f78bb1c5389707b3ceedfbc1ae43549) )
    ROM_LOAD( "1673a.prom-u4",  0x2000, 0x0800, CRC(1495ce3d) SHA1(3189f8061961d90a52339c855c06e81f4537fb2b) )
    ROM_LOAD( "1674a.prom-u5",  0x2800, 0x0800, CRC(ab7fc5d9) SHA1(b2d9241d83d175ead4da36d7311a41a5f972e06a) )
    ROM_LOAD( "1675a.prom-u6",  0x3000, 0x0800, CRC(cf5e5016) SHA1(78a3f1e4a905515330d4737ac38576ac6e0d8611) )
    ROM_LOAD( "1676a.prom-u7",  0x3800, 0x0800, CRC(b61a3ab3) SHA1(0f4ef5c7fe299ad20fa4637260282a733f1cf461) )
    ROM_LOAD( "1677a.prom-u8",  0x4000, 0x0800, CRC(bc0273b1) SHA1(8e8d8830f17b9fa6d45d98108ca02d90c29de574) )
    ROM_LOAD( "1678b.prom-u9",  0x4800, 0x0800, CRC(7894da98) SHA1(2de7c121ad847e51a10cb1b81aec84cc44a3d04c) )
    ROM_LOAD( "1679a.prom-u10", 0x5000, 0x0800, CRC(db865654) SHA1(db4d5675b53ff2bbaf70090fd064e98862f4ad33) )
    ROM_LOAD( "1680a.prom-u11", 0x5800, 0x0800, CRC(2c2454de) SHA1(74101806439c9faeba88ffe573fa4f93ffa0ba3c) )
    ROM_LOAD( "1681a.prom-u12", 0x6000, 0x0800, CRC(77028885) SHA1(bc981620ebbfbe4e32b3b4d00504475634454c57) )
    ROM_LOAD( "1682a.prom-u13", 0x6800, 0x0800, CRC(babe5cf1) SHA1(26219b7a26f818fee2fe579ec6fb0b16c6bf056f) )
    ROM_LOAD( "1683a.prom-u14", 0x7000, 0x0800, CRC(1b98b618) SHA1(19854cb2741ba37c11ae6d429fa6c17ff930f5e5) )
    ROM_LOAD( "1684a.prom-u15", 0x7800, 0x0800, CRC(cb3ded3b) SHA1(f1e886f4f71b0f6f2c11fb8b4921c3452fc9b2c0) )
    ROM_LOAD( "1685a.prom-u16", 0x8000, 0x0800, CRC(43016a79) SHA1(ee22c1fe0c8df90d9215175104f8a796c3d2aed3) )
    ROM_LOAD( "1686a.prom-u17", 0x8800, 0x0800, CRC(a4397772) SHA1(cadc95b869f5bf5dba7f03dfe5ae64a50899cced) )
    ROM_LOAD( "1687a.prom-u18", 0x9000, 0x0800, CRC(002f3bc4) SHA1(7f3795a05d5651c90cdcd4d00c46d05178b433ea) )
    ROM_LOAD( "1688a.prom-u19", 0x9800, 0x0800, CRC(0326d87a) SHA1(3a5ea4526db417b9e00b24b019c1c6016773c9e7) )
    ROM_LOAD( "1709a.prom-u20", 0xa000, 0x0800, CRC(f35ed1ec) SHA1(dce95a862af0c6b67fb76b99fee0523d53b7551c) )
    ROM_LOAD( "1710a.prom-u21", 0xa800, 0x0800, CRC(6203be22) SHA1(89731c7c88d0125a11368d707f566eb53c783266) )

    ROM_REGION( 0x0420, "proms", 0 )
    ROM_LOAD( "s-c.xyt-u39",    0x0000, 0x0400, CRC(56484d19) SHA1(61f43126fdcfc230638ed47085ae037a098e6781) )  // sine table
    ROM_LOAD( "pr-82.cpu-u15",  0x0400, 0x0020, CRC(c609b79e) SHA1(49dbcbb607079a182d7eb396c0da097166ea91c9) )  // CPU board addressing
ROM_END

PROM以外はCRC32が一致しました!

$ ./crc
Usage: crc filename crc32 size [addr]
$ ./crc TACSCAN.ROM 0da13158 800
crc32=0da13158,size=0800
crc32=0da13158,addr=0000-07ff
$ ./crc TACSCAN.ROM 98de6fd5 800
crc32=98de6fd5,size=0800
crc32=98de6fd5,addr=0800-0fff
$ ./crc TACSCAN.ROM dc400074 800
crc32=dc400074,size=0800
crc32=dc400074,addr=1000-17ff
$ ./crc TACSCAN.ROM 2caf6f7e 800
crc32=2caf6f7e,size=0800
crc32=2caf6f7e,addr=1800-1fff
$ ./crc TACSCAN.ROM 1495ce3d 800
crc32=1495ce3d,size=0800
crc32=1495ce3d,addr=2000-27ff
$ ./crc TACSCAN.ROM ab7fc5d9 800
crc32=ab7fc5d9,size=0800
crc32=ab7fc5d9,addr=2800-2fff
$ ./crc TACSCAN.ROM cf5e5016 800
crc32=cf5e5016,size=0800
crc32=cf5e5016,addr=3000-37ff
$ ./crc TACSCAN.ROM b61a3ab3 800
crc32=b61a3ab3,size=0800
crc32=b61a3ab3,addr=3800-3fff
$ ./crc TACSCAN.ROM bc0273b1 800
crc32=bc0273b1,size=0800
crc32=bc0273b1,addr=4000-47ff
$ ./crc TACSCAN.ROM 7894da98 800
crc32=7894da98,size=0800
crc32=7894da98,addr=4800-4fff
$ ./crc TACSCAN.ROM db865654 800
crc32=db865654,size=0800
crc32=db865654,addr=5000-57ff
$ ./crc TACSCAN.ROM 2c2454de 800
crc32=2c2454de,size=0800
crc32=2c2454de,addr=5800-5fff
$ ./crc TACSCAN.ROM 77028885 800
crc32=77028885,size=0800
crc32=77028885,addr=6000-67ff
$ ./crc TACSCAN.ROM babe5cf1 800
crc32=babe5cf1,size=0800
crc32=babe5cf1,addr=6800-6fff
$ ./crc TACSCAN.ROM 1b98b618 800
crc32=1b98b618,size=0800
crc32=1b98b618,addr=7000-77ff
$ ./crc TACSCAN.ROM cb3ded3b 800
crc32=cb3ded3b,size=0800
crc32=cb3ded3b,addr=7800-7fff
$ ./crc TACSCAN.ROM 43016a79 800
crc32=43016a79,size=0800
crc32=43016a79,addr=8000-87ff
$ ./crc TACSCAN.ROM a4397772 800
crc32=a4397772,size=0800
crc32=a4397772,addr=8800-8fff
$ ./crc TACSCAN.ROM 002f3bc4 800
crc32=002f3bc4,size=0800
crc32=002f3bc4,addr=9000-97ff
$ ./crc TACSCAN.ROM 0326d87a 800
crc32=0326d87a,size=0800
crc32=0326d87a,addr=9800-9fff
$ ./crc TACSCAN.ROM f35ed1ec 800
crc32=f35ed1ec,size=0800
crc32=f35ed1ec,addr=a000-a7ff
$ ./crc TACSCAN.ROM 6203be22 800
crc32=6203be22,size=0800
crc32=6203be22,addr=a800-afff
$ ./crc TACSCAN.ROM 56484d19 400
crc32=56484d19,size=0400
$ ./crc TACSCAN.ROM c609b79e 20
crc32=c609b79e,size=0020

リネームしたものをtacscan.zipにまとめて、mame/roms配下に置いて起動してみます。

>mame.exe tacscan
s-c.xyt-u39 NOT FOUND (tried in tacscan)
pr-82.cpu-u15 NOT FOUND (tried in tacscan)
Fatal error: Required files are missing, the machine cannot be run.

最近のMAMEではROMが不足しているようですが、古いMAME(mame070b.zip)ではPROMは不要です。

Atari 50を解析してみた

Atari 50がセールのようです! store.steampowered.com

ここらへんを参考にして解析してみる。 github.com

>cowabunga64.exe -k atari assets.pie assets.zip

例えばMAJOR HAVOCであれば、assets.zipを解凍してroms/arcade/MajorHavoc.romです。

CRCを調べて切り出します。 https://github.com/mamedev/mame/blob/master/src/mame/atari/mhavoc.cpp

/*************************************
 *
 *  ROM definitions
 *
 *************************************/

/*
 * Notes:
 * the R3 roms are supported as "mhavoc", the R2 roms (with a bug in gameplay)
 * are supported as "mhavoc2".
 * "Return to Vax" - Jess Askey's souped up version (errors on self test)
 * are supported as "mhavocrv".
 * Prototype is supported as "mhavocp"
 * Alpha one is a single-board prototype
 */

ROM_START( mhavoc )
    // Alpha Processor ROMs
    // Vector Generator ROM
    ROM_REGION( 0x2000, "vectorrom", 0 )
    ROM_LOAD( "136025.210",   0x0000, 0x2000, CRC(c67284ca) SHA1(d9adad80c266d36429444f483cac4ebcf1fec7b8) )

    // Program ROM
    ROM_REGION( 0x18000, "alpha", 0 )
    ROM_LOAD( "136025.216",   0x08000, 0x4000, CRC(522a9cc0) SHA1(bbd75e01c45220e1c87bd1e013cf2c2fb9f376b2) )
    ROM_LOAD( "136025.217",   0x0c000, 0x4000, CRC(ea3d6877) SHA1(27823c1b546c073b37ff11a8cb25312ea71673c2) )

    // Paged Program ROM
    ROM_LOAD( "136025.215",   0x10000, 0x4000, CRC(a4d380ca) SHA1(c3cdc76054be2f904b1fb6f28c3c027eba5c3a70) ) // page 0+1
    ROM_LOAD( "136025.318",   0x14000, 0x4000, CRC(ba935067) SHA1(05ad81e7a1982b9d8fddb48502546f48b5dc21b7) ) // page 2+3

    // Paged Vector Generator ROM
    ROM_REGION( 0x8000, "avg", 0 )
    ROM_LOAD( "136025.106",   0x0000, 0x4000, CRC(2ca83c76) SHA1(cc1adca32f70af30c4590e9fd6b056b051ccdb38) ) // page 0+1
    ROM_LOAD( "136025.107",   0x4000, 0x4000, CRC(5f81c5f3) SHA1(be4055727a2d4536e37ec20150deffdb5af5b01f) ) // page 2+3

    // Gamma Processor ROM
    ROM_REGION( 0x4000, "gamma", 0 )
    ROM_LOAD( "136025.108",   0x0000, 0x4000, CRC(93faf210) SHA1(7744368a1d520f986d1c4246113a7e24fcdd6d04) ) // mirrored to c000-ffff for reset+interrupt vectors

    // AVG PROM
    ROM_REGION( 0x100, "avg:prom", 0 )
    ROM_LOAD( "136002-125.6c",0x0000, 0x0100, CRC(5903af03) SHA1(24bc0366f394ad0ec486919212e38be0f08d0239) )
ROM_END

AVG PROM以外はCRC32が一致しました!

$ ./crc
Usage: crc filename crc32 size [addr]
$ ./crc MajorHavoc.rom 522a9cc0 4000
crc32=522a9cc0,size=4000
crc32=522a9cc0,addr=0000-3fff
$ ./crc MajorHavoc.rom ea3d6877 4000
crc32=ea3d6877,size=4000
crc32=ea3d6877,addr=4000-7fff
$ ./crc MajorHavoc.rom a4d380ca 4000
crc32=a4d380ca,size=4000
crc32=a4d380ca,addr=8000-bfff
$ ./crc MajorHavoc.rom ba935067 4000
crc32=ba935067,size=4000
crc32=ba935067,addr=c000-ffff
$ ./crc MajorHavoc.rom 2ca83c76 4000
crc32=2ca83c76,size=4000
crc32=2ca83c76,addr=16000-19fff
$ ./crc MajorHavoc.rom 5f81c5f3 4000
crc32=5f81c5f3,size=4000
crc32=5f81c5f3,addr=1a000-1dfff
$ ./crc MajorHavoc.rom 93faf210 4000
crc32=93faf210,size=4000
crc32=93faf210,addr=10000-13fff
$ ./crc MajorHavoc.rom 5903af03 100
crc32=5903af03,size=0100

リネームしたものをmhavoc.zipにまとめて、mame/roms配下に置いて起動してみます。

>mame.exe mhavoc
136002-125.6c NOT FOUND (tried in mhavoc)
Fatal error: Required files are missing, the machine cannot be run.

最近のMAMEではROMが不足※しているようですが、古いMAME(mame070b.zip)ではAVG PROMは不要です。 www.mamedev.org

※QuantumにAVG PROMが含まれていました。roms/quantum/quantum.avg_promです。MAJOR HAVOCとも共通になります。

Amazonデータレコーダーの使い方

セールでうっかり購入したAmazonデータレコーダーを攻略しました。

www.amazon.co.jp

まずはTAPE部分ですが、CLOADは50%くらいしか成功しないし、CSAVEは100%失敗する。よってデータレコーダーの置き換えにはならない。

つぎにUSB部分ですが、CLOADは音量MAXに合わせてWAVファイルを準備する事で100%成功する。CSAVEは録音したWAVファイルをPCで加工する必要がある。

SDカードをFATまたはFAT32でフォーマットします。例えばPC-6001Vで遊べるSPY PANIC(SPY.P6T)があったとします。これをBASIC部分(SPY1.P6)とマシン語部分(SPY2.P6)のベタファイルに分割して、p6towav.exeでWAVファイルにします。

>p6towav.exe SPY1.P6 SPY1.WAV
>p6towav.exe -f io SPY2.P6 SPY2.WAV

つぎにffmpeg.exeでフォーマットや音量MAXに合わせてレベルを調整します。

>ffmpeg -i SPY1.WAV -ac 2 -ar 32000 -af volume=0.3 -c:a adpcm_ms SPY01.WAV
>ffmpeg -i SPY2.WAV -ac 2 -ar 32000 -af volume=0.3 -c:a adpcm_ms SPY02.WAV

AmazonデータレコーダーのPLAY/REC/REMOTEをPC-6001本体とつなぎますが、PLAYは分岐させてMONITOR用のスピーカーにつなぎます。

PC-6001本体でCLOADすれば、SPY01.WAVが再生されます。BASIC部分をRUNすると、マシン語ロードのタイミングで、SPY01.WAVの最後の部分がふたたび再生されますので、次の曲ボタンでSPY02.WAVを再生します。

REMOTE端子で曲がループしてしまう問題があるので、PLAY/RECだけをPC-6001本体とつなぐのもありだと思いました。

PC-6001FでRAM&RAMカートリッジ

PC-6001FではPC-6001/PC-6001 mk2/PC-6601にも対応しています

sbeach.seesaa.net

手持ちのモニタで表示が右寄りなのを直したい。以下の704を784に修正

VGAOUT.vhd

  vga_hs_i <=  '0' when (LCDMODE = '0' and  784 <= hcntrd and hcntrd <  800) else
                '0' when (LCDMODE = '1' and    0 <= hcntrd and hcntrd <    4) else
                '1';

64KB RAM&RAMカートリッジ相当の機能を実装したい

INTWAITGEN.vhd

-- I/O port F0H-F8H
    process (CLK16M,RSTN)
    begin
        if (rstn = '0') then
            port_f0_reg <= "01110001";
            port_f1_reg <= "11011101";
--         port_f2_reg <= "01010000";
            port_f2_reg <= "01010101";
            port_f3_reg <= "11000010";
            port_f4_reg <= "00000000";
            port_f5_reg <= "00000000";
            port_f6_reg <= "00000011";
            port_f7_reg <= "00000110";
            port_f8_reg <= "11000011";
        elsif (CLK16M'event and CLK16M = '1') then
--         if (MK2MODE = '1' and A(7 downto 4) = "1111") then
            if (A(7 downto 4) = "1111") then
                if (IORQN = '0' and WRN = '0') then
                    case a_low is
                        when "0000" => port_f0_reg <= DI;
                        when "0001" => port_f1_reg <= DI;
                        when "0010" => port_f2_reg <= DI;
                        when "0011" => port_f3_reg <= DI;
                        when "0100" => port_f4_reg <= DI;
                        when "0101" => port_f5_reg <= DI;
                        when "0110" => port_f6_reg <= DI;
                        when "0111" => port_f7_reg <= DI;
                        when "1000" => port_f8_reg <= DI;
                        when others => null;
                    end case;
                end if;
            end if;
        end if;
    end process;

:

    DO_PORT_F0H <= RAMDI       when (a_low = "1101" and EXKANJIENB = '1') else
                    RAMDI       when (a_low = "1110" and EXKANJIENB = '1') else
--                 "11111111"  when (MK2MODE = '0') else
                    port_f0_reg when (a_low = "0000") else
                    port_f1_reg when (a_low = "0001") else
                    port_f2_reg when (a_low = "0010") else
                    port_f3_reg when (a_low = "0011") else
                    port_f4_reg when (a_low = "0100") else
                    port_f5_reg when (a_low = "0101") else
                    port_f6_reg when (a_low = "0110") else
                    port_f7_reg when (a_low = "0111") else
                    port_f8_reg when (a_low = "1000") else
                    "11111111";

PC6001.vhd

-- main ram and SDRAM control
    sdram_add <=
        sd_rdad                                when (sd_sdenb = '1') else
        "010" & "1010" & vdg_cga               when (mk2p66m = '0' and busackn = '0' and vdg_cgenbn = '0') else
        "000" & "1" & (not vramsw2) & vdg_a    when (mk2p66m = '0' and busackn = '0' and vdg_cgenbn = '1') else
        "11" & exkanjiadd                      when (mk2p66m = '0' and exkanjiaddenb = '1') else
-- 【修正】初代機モード: チップセレクト信号(intramcsn/extramcsn)によるバンク切り替えを有効化
        "000" & cpu_a_f2                       when (mk2p66m = '0' and intramcsn = '0') else -- ポートF0hで内蔵RAM指定時
        "001" & cpu_a_f2                       when (mk2p66m = '0' and extramcsn = '0') else -- ポートF0hで外部RAM指定時             
        "010" & "1010" & cpu_a_f2(11 downto 0) when (mk2p66m = '0' and cgswn = '0' and cpu_a_f2(15 downto 13) = "011") else
        "000" & "1"    & cpu_a_f2(14 downto 0) when (mk2p66m = '0' and cpu_a_f2(15) = '1') else
        "010" & "00"   & cpu_a_f2(13 downto 0) when (mk2p66m = '0' and cpu_a_f2(15 downto 14) = "00") else
        "010" & "11"   & cpu_a_f2(13 downto 0) when (mk2p66m = '0' and cpu_a_f2(15 downto 14) = "01") else
        "000" & crtc_a                         when (mk2p66m = '1' and busackn = '0' and crtc_cgrd = '0') else
        "011" & "10"  & crtc_a(13 downto 0)    when (mk2p66m = '1' and busackn = '0' and crtc_cgrd = '1') else
        "11" & exkanjiadd                      when (mk2p66m = '1' and exkanjiaddenb = '1') else
        "011" & "100"  & cpu_a_f2(12 downto 0) when (mk2p66m = '1' and memc_cgromcsn = '0' and charmode = '0') else
        "011" & "101"  & cpu_a_f2(12 downto 0) when (mk2p66m = '1' and memc_cgromcsn = '0' and charmode = '1') else
        "011" & "0"    & cpu_a_f2(14 downto 0) when (mk2p66m = '1' and basicromcsn = '0') else
        "011" & "11"   & cpu_a_f2(13 downto 0) when (mk2p66m = '1' and vo_knromcsn = '0' and vo_knsel = '0') else
        "100" & "0" & kanjisel & cpu_a_f2(13 downto 0) when (mk2p66m = '1' and vo_knromcsn = '0' and vo_knsel = '1') else
        "010" & "110"  & cpu_a_f2(12 downto 0) when (mk2p66m = '1' and slot2romcsn = '0') else
        "010" & "111"  & cpu_a_f2(12 downto 0) when (mk2p66m = '1' and slot3romcsn = '0') else
        "000" & cpu_a_f2                       when (mk2p66m = '1' and intramcsn = '0') else
        "001" & cpu_a_f2                       when (mk2p66m = '1' and extramcsn = '0') else
        (others => '0');

    sdram_rdn <=
        (vdg_rdn and vdg_cgrdn) when (mk2p66m = '0' and busackn = '0') else
        crtc_rdn                when (mk2p66m = '1' and busackn = '0') else
        '0'                     when (exkanjiaddenb = '1') else
        memrdn                  when (mk2p66m = '0') else
        memc_rdn                when (mk2p66m = '1') else
        '1';

    sdram_wrn <=
        sd_sdwrn when (sd_sdenb = '1') else
        '1'      when (busackn = '0') else
-- 【修正】初代機モード: チップセレクト信号に基づいて書き込みを許可
        memwrn   when (mk2p66m = '0' and (intramcsn = '0' or extramcsn = '0')) else -- ポートF2hで書き込み許可時       
        memwrn   when (mk2p66m = '0' and mem16k  = '1' and cpu_a_f2(15 downto 14) = "11") else
        memwrn   when (mk2p66m = '0' and mem16k  = '0' and cpu_a_f2(15) = '1') else
        memc_wrn when (mk2p66m = '1') else
        '1';