remote488: implemented synchronization by checkpoints

This commit is contained in:
fulivi 2018-05-23 10:24:17 +02:00
parent b03df3659d
commit d55e7f910f
2 changed files with 120 additions and 43 deletions

View File

@ -43,6 +43,8 @@
| Q | O | No | Request 'P' msg |
| R | I/O | Yes | Set bus control signals to 0 |
| S | I/O | Yes | Set bus control signals to 1 |
| X | I/O | No | Checkpoint in byte string |
| Y | I/O | Yes | Checkpoint reached |
- D messages
@ -50,24 +52,42 @@
In the output direction the remotizer implements an acceptor so
that the proper 3-way handshake is implemented with the
source. The acceptor has no delays between state transitions so
that it can keep up with any speed of the source. The acceptor
doesn't wait for any acknowledgement from external receiver. The
buffering in the output stream must have enough space to hold
bursts of data bytes before they are processed by the external
receiver.
that it can keep up with any speed of the source.
In the input direction a source is implemented to handshake with
local acceptor(s). The FSM in the source has no delays so that it can
operate at whatever speed the slowest acceptor on the bus can sustain.
Bytes are removed from the input buffer of the stream as the local
acceptors acknowledge them. No input message is processed by the
remotizer when it's waiting for 3-way handshake with acceptors to
complete. If a local controller asserts the ATN signal to regain
control of the bus when there are input bytes waiting, the bytes
are discarded.
complete.
Byte values are expressed in positive logic (which is the opposite
of what's sent on the bus). To discriminate between DAB and command
bytes the receiver of this message has to check its state of ATN
signal (see R&S messages).
A simple infrastructure for synchronization between near and far
end is provided. The sender of a string of bytes can insert a
"checkpoint" msg in the string. This msg is turned into a
"checkpoint reached" msg by the receiver and sent back. This msg
reports to the sender that the receiver has processed data up to
the last checkpoint.
The acceptor in the remotizer sends a checkpoint in two cases: when a
byte with EOI is accepted and sent to the far end or when there's
a pause in the byte string longer than 10 ms. This is just for
safety, normal byte strings are always terminated by EOI by the
sender. The remotizer acceptor stops accepting data after sending
a checkpoint. It resumes when the "checkpoint reached" msg is sent
back. Bytes of I/F commands never use checkpointing.
The source handshake (SH) in the remotizer accepts data bytes from
the far end and puts them on the local bus. A simple heuristic is
implemented to recognize the situation when the far end has sent
more data than a local acceptor is willing to take. Whenever ATN
is asserted by a local device (the controller) all input bytes are
discarded up to the next checkpoint. The "checkpoint reached" msg
then reports that data has been completely/partially flushed by
setting its byte to 1. In normal cases byte is set to 0.
Usage of checkpointing is optional. The minimum the far end should
implement is sending back a "checkpoint reached" msg whenever a
checkpoint is received.
- E messages
@ -129,16 +149,31 @@
message is attempting to set to 1 a signal that is locally forced
to 0.
- X & Y messages
See "D" messages above. "X" message is sent to request the
receiver to acknowledge the reception by sending back a "Y"
message. When used, the "X" message should be sent at the end of
a string of bytes by the sender. The reception of "Y" message
confirms that the receiver has seen (though not necessarily
accepted) the whole string.
The "X" -> "Y" sequence is used in the local AH for flow control:
once "X" is sent out, the acceptor pauses accepting bytes until a
"Y" is received. For this reason the far end should always
implement, at the very least, the sending back of "Y" whenever "X"
is seen.
A "X" message carries no info in its byte. A "Y" message reports
whether the preceding byte string was entirely accepted by the
receiver or not (with a 00 or 01 value in the associated byte,
respectively).
Limits & potential issues:
- There are no checks for violations of IEEE-488 protocol. It's
possible, for example, to put the bus in an illegal state by
sending a 'E' msg when the local controller is asserting ATN
(thus creating a phantom parallel poll).
- The unacknowledged nature of D/E msgs may cause problems if
the sender of the msgs must be synchronized byte-for-byte with
the receiver. There is no direct way, for example, for the
sender of a string of bytes to know how many have been accepted
by the receiver.
- The heuristic to discard byte strings in the SH could be a bit
too simple and do the wrong thing in few rare cases.
- It's difficult to achieve accurate synchronization between the
local emulation time and the external time.
@ -146,10 +181,6 @@
- Implement handling of incoming Q msgs (needed when parallel poll
is being performed by a remote controller)
- Enhancement: implement a msg for accurate time synchronization
- Enhancement: implement some form of sliding window acknowledgement
for cases when sender has to know how many bytes have been
processed by the receiver. The HP "Amigo" protocol I used for
my experiments has no such need.
*********************************************************************/
@ -199,11 +230,14 @@ constexpr char MSG_PP_DATA = 'P'; // I: Parallel poll data
constexpr char MSG_PP_REQUEST = 'Q'; // O: Request PP data
constexpr char MSG_ECHO_REQ = 'J'; // O: Heartbeat msg: echo request
constexpr char MSG_ECHO_REPLY = 'K'; // I: Heartbeat msg: echo reply
constexpr char MSG_CHECKPOINT = 'X'; // I/O: Checkpoint in byte stream
constexpr char MSG_CP_REACHED = 'Y'; // I/O: Checkpoint reached
// Timings
constexpr unsigned POLL_PERIOD_US = 20; // Poll period (µs)
constexpr unsigned HEARTBEAT_MS = 500; // Heartbeat ping period (ms)
constexpr unsigned MAX_MISSED_HB = 3; // Missed heartbeats to declare the connection dead
constexpr unsigned AH_TO_MS = 10; // Timeout in AH to report a byte string terminated (ms)
// device type definition
DEFINE_DEVICE_TYPE(REMOTE488, remote488_device, "remote488", "IEEE-488 Remotizer")
@ -244,6 +278,7 @@ void remote488_device::ieee488_ifc(int state)
update_signal(SIGNAL_IFC_BIT , state);
if (!state) {
LOG("IFC\n");
flush_data();
bus_reset();
}
}
@ -257,6 +292,7 @@ void remote488_device::ieee488_atn(int state)
{
update_signal(SIGNAL_ATN_BIT , state);
update_sh_fsm();
update_ah_fsm();
update_pp();
}
@ -269,6 +305,7 @@ void remote488_device::device_start()
{
m_poll_timer = timer_alloc(TMR_ID_POLL);
m_hb_timer = timer_alloc(TMR_ID_HEARTBEAT);
m_ah_timer = timer_alloc(TMR_ID_AH);
}
void remote488_device::device_reset()
@ -287,6 +324,9 @@ void remote488_device::device_reset()
m_connected = true;
set_connection(false);
m_ibf = false;
m_flush_bytes = false;
m_waiting_cp = false;
bus_reset();
}
@ -295,8 +335,6 @@ void remote488_device::bus_reset()
m_sh_state = REM_SH_SIDS;
m_ah_state = REM_AH_ACRS;
m_rx_state = REM_RX_WAIT_CH;
m_ibf = false;
m_flush_bytes = false;
m_poll_timer->adjust(attotime::from_usec(POLL_PERIOD_US) , 0 , attotime::from_usec(POLL_PERIOD_US));
m_pp_data = 0;
m_pp_requested = false;
@ -315,38 +353,25 @@ void remote488_device::process_input_msgs()
switch (msg_ch) {
case MSG_SIGNAL_CLEAR:
m_flush_bytes = false;
update_signals_from_rem(0 , data);
break;
case MSG_SIGNAL_SET:
m_flush_bytes = false;
update_signals_from_rem(data , 0);
break;
case MSG_DATA_BYTE:
if (m_flush_bytes) {
LOG("Flushed\n");
} else {
m_poll_timer->reset();
recvd_data_byte(data , false);
return;
}
break;
case MSG_END_BYTE:
if (m_flush_bytes) {
LOG("Flushed\n");
m_flush_bytes = false;
m_poll_timer->adjust(attotime::zero);
} else {
m_poll_timer->reset();
recvd_data_byte(data , true);
return;
recvd_data_byte(data , msg_ch == MSG_END_BYTE);
}
break;
return;
case MSG_PP_DATA:
m_flush_bytes = false;
m_pp_data = data;
m_pp_requested = false;
update_pp_dio();
@ -356,6 +381,18 @@ void remote488_device::process_input_msgs()
send_update(MSG_ECHO_REPLY , 0);
break;
case MSG_CHECKPOINT:
send_update(MSG_CP_REACHED , m_flush_bytes);
m_flush_bytes = false;
break;
case MSG_CP_REACHED:
if (m_waiting_cp) {
m_waiting_cp = false;
update_ah_fsm();
}
break;
default:
break;
}
@ -379,6 +416,13 @@ void remote488_device::device_timer(emu_timer &timer, device_timer_id id, int pa
send_update(MSG_ECHO_REQ , 0);
break;
case TMR_ID_AH:
if (!m_waiting_cp) {
LOG("CP T/O\n");
ah_checkpoint();
}
break;
default:
break;
}
@ -419,10 +463,23 @@ void remote488_device::recvd_data_byte(uint8_t data , bool eoi)
m_ib = data;
m_ib_eoi = eoi;
m_ibf = true;
if (is_local_atn_active()) {
flush_data();
}
update_sh_fsm();
update_ah_fsm();
}
void remote488_device::flush_data()
{
if (m_ibf) {
LOG("Flushing enabled\n");
m_flush_bytes = true;
m_ibf = false;
m_poll_timer->adjust(attotime::zero);
}
}
void remote488_device::update_signals_from_rem(uint8_t to_set , uint8_t to_clear)
{
uint8_t diff = m_in_signals;
@ -473,11 +530,8 @@ void remote488_device::update_state(uint8_t new_signals)
m_out_signals = new_signals;
if (is_local_atn_active() && m_ibf) {
LOG("Flushing enabled\n");
m_flush_bytes = true;
m_ibf = false;
m_poll_timer->adjust(attotime::zero);
if (is_local_atn_active()) {
flush_data();
}
if (to_set) {
@ -521,7 +575,9 @@ bool remote488_device::is_msg_type(char c)
c == MSG_DATA_BYTE ||
c == MSG_END_BYTE ||
c == MSG_PP_DATA ||
c == MSG_ECHO_REPLY;
c == MSG_ECHO_REPLY ||
c == MSG_CHECKPOINT ||
c == MSG_CP_REACHED;
}
bool remote488_device::is_terminator(char c)
@ -617,6 +673,13 @@ bool remote488_device::is_local_atn_active() const
return !BIT(m_out_signals , SIGNAL_ATN_BIT) && BIT(m_in_signals , SIGNAL_ATN_BIT);
}
void remote488_device::ah_checkpoint()
{
m_waiting_cp = true;
m_ah_timer->reset();
send_update(MSG_CHECKPOINT , 0);
}
void remote488_device::update_ah_fsm()
{
bool changed = true;
@ -640,13 +703,22 @@ void remote488_device::update_ah_fsm()
break;
case REM_AH_ACDS:
{
if (m_bus->dav_r()) {
m_ah_state = REM_AH_ACRS;
} else if (!m_waiting_cp) {
uint8_t dio = ~m_bus->dio_r();
if (!m_bus->eoi_r()) {
send_update(MSG_END_BYTE , dio);
ah_checkpoint();
} else {
send_update(MSG_DATA_BYTE , dio);
if (!BIT(m_out_signals , SIGNAL_ATN_BIT)) {
// I/F commands have no checkpoint
m_ah_timer->reset();
} else {
m_ah_timer->adjust(attotime::from_msec(AH_TO_MS));
}
}
m_ah_state = REM_AH_AWNS;
}

View File

@ -34,7 +34,8 @@ public:
// Timers
enum {
TMR_ID_POLL,
TMR_ID_HEARTBEAT
TMR_ID_HEARTBEAT,
TMR_ID_AH
};
protected:
@ -93,17 +94,20 @@ private:
bool m_ib_eoi;
emu_timer *m_poll_timer;
emu_timer *m_hb_timer;
emu_timer *m_ah_timer;
unsigned m_connect_cnt;
bool m_connected;
uint8_t m_pp_data;
bool m_pp_requested;
uint8_t m_pp_dio;
uint8_t m_sh_dio;
bool m_waiting_cp;
void bus_reset();
void process_input_msgs();
void set_connection(bool state);
void recvd_data_byte(uint8_t data , bool eoi);
void flush_data();
void update_signals_from_rem(uint8_t to_set , uint8_t to_clear);
void update_signal(signal_bit bit , int state);
void update_state(uint8_t new_signals);
@ -114,6 +118,7 @@ private:
static bool is_space(char c);
char recv_update(uint8_t& data);
bool is_local_atn_active() const;
void ah_checkpoint();
void update_ah_fsm();
void update_sh_fsm();
bool is_local_pp_active() const;