diff --git a/interface2/firmware/include/coax.h b/interface2/firmware/include/coax.h
index 85d53e2..f7c17a8 100644
--- a/interface2/firmware/include/coax.h
+++ b/interface2/firmware/include/coax.h
@@ -28,6 +28,12 @@
#define COAX_ERROR_NOT_INITIALIZED -1024
+enum class CoaxProtocol
+{
+ _3270 = 0,
+ _3299 = 1
+};
+
enum class CoaxParity
{
Odd = 0,
@@ -39,13 +45,17 @@ class SPICoaxTransceiver;
class Coax
{
public:
- Coax(SPICoaxTransceiver &spiCoaxTransceiver, CoaxParity parity,
- volatile uint16_t *buffer, size_t bufferSize);
+ Coax(SPICoaxTransceiver &spiCoaxTransceiver, volatile uint16_t *buffer,
+ size_t bufferSize);
bool init();
void reset();
+ void setTXProtocol(CoaxProtocol protocol);
+ void setRXProtocol(CoaxProtocol protocol);
+ void setParity(CoaxParity parity);
+
int transmit(const uint16_t *buffer, size_t bufferCount);
int receive(uint16_t *buffer, size_t bufferSize, uint16_t timeout);
@@ -53,6 +63,8 @@ public:
private:
SPICoaxTransceiver &_spiCoaxTransceiver;
+ CoaxProtocol _txProtocol;
+ CoaxProtocol _rxProtocol;
CoaxParity _parity;
bool _isInitialized;
@@ -79,7 +91,9 @@ private:
#define COAX_REGISTER_CONTROL 0x2
#define COAX_REGISTER_CONTROL_LOOPBACK 0x01
+#define COAX_REGISTER_CONTROL_TX_PROTOCOL 0x04
#define COAX_REGISTER_CONTROL_TX_PARITY 0x08
+#define COAX_REGISTER_CONTROL_RX_PROTOCOL 0x20
#define COAX_REGISTER_CONTROL_RX_PARITY 0x40
#define COAX_REGISTER_DEVICE_ID 0xf
@@ -100,7 +114,9 @@ public:
int receive(uint16_t *buffer, size_t bufferSize);
void setLoopback(bool loopback);
+ void setTXProtocol(CoaxProtocol protocol);
void setTXParity(CoaxParity parity);
+ void setRXProtocol(CoaxProtocol protocol);
void setRXParity(CoaxParity parity);
inline bool isTXComplete()
diff --git a/interface2/firmware/include/interface.h b/interface2/firmware/include/interface.h
index a7b4300..53c27cc 100644
--- a/interface2/firmware/include/interface.h
+++ b/interface2/firmware/include/interface.h
@@ -34,6 +34,8 @@
#define INFO_MESSAGE_BUFFER_SIZE 0x06
#define INFO_FEATURES 0x07
+#define FEATURE_PROTOCOL_3299 0x10
+
#define TEST_SUPPORTED_TESTS 0x01
#define ERROR_INVALID_MESSAGE 1
diff --git a/interface2/firmware/src/coax.cpp b/interface2/firmware/src/coax.cpp
index cb8f76a..c13215b 100644
--- a/interface2/firmware/src/coax.cpp
+++ b/interface2/firmware/src/coax.cpp
@@ -21,10 +21,12 @@
#include "coax.h"
-Coax::Coax(SPICoaxTransceiver &spiCoaxTransceiver, CoaxParity parity,
- volatile uint16_t *buffer, size_t bufferSize) :
+Coax::Coax(SPICoaxTransceiver &spiCoaxTransceiver, volatile uint16_t *buffer,
+ size_t bufferSize) :
_spiCoaxTransceiver(spiCoaxTransceiver),
- _parity(parity),
+ _txProtocol(CoaxProtocol::_3270),
+ _rxProtocol(CoaxProtocol::_3270),
+ _parity(CoaxParity::Even),
_buffer(buffer),
_bufferSize(bufferSize)
{
@@ -41,7 +43,10 @@ bool Coax::init()
return false;
}
+ _spiCoaxTransceiver.setTXProtocol(_txProtocol);
_spiCoaxTransceiver.setTXParity(_parity);
+
+ _spiCoaxTransceiver.setRXProtocol(_rxProtocol);
_spiCoaxTransceiver.setRXParity(_parity);
_isInitialized = true;
@@ -59,10 +64,62 @@ void Coax::reset()
_spiCoaxTransceiver.reset();
+ _spiCoaxTransceiver.setTXProtocol(_txProtocol);
_spiCoaxTransceiver.setTXParity(_parity);
+
+ _spiCoaxTransceiver.setRXProtocol(_rxProtocol);
_spiCoaxTransceiver.setRXParity(_parity);
}
+void Coax::setTXProtocol(CoaxProtocol protocol)
+{
+ if (!_isInitialized) {
+ _txProtocol = protocol;
+ return;
+ }
+
+ if (_txProtocol == protocol) {
+ return;
+ }
+
+ _spiCoaxTransceiver.setTXProtocol(protocol);
+
+ _txProtocol = protocol;
+}
+
+void Coax::setRXProtocol(CoaxProtocol protocol)
+{
+ if (!_isInitialized) {
+ _rxProtocol = protocol;
+ return;
+ }
+
+ if (_rxProtocol == protocol) {
+ return;
+ }
+
+ _spiCoaxTransceiver.setRXProtocol(protocol);
+
+ _rxProtocol = protocol;
+}
+
+void Coax::setParity(CoaxParity parity)
+{
+ if (!_isInitialized) {
+ _parity = parity;
+ return;
+ }
+
+ if (_parity == parity) {
+ return;
+ }
+
+ _spiCoaxTransceiver.setTXParity(parity);
+ _spiCoaxTransceiver.setRXParity(parity);
+
+ _parity = parity;
+}
+
int Coax::transmit(const uint16_t *buffer, size_t bufferCount)
{
if (!_isInitialized) {
@@ -434,11 +491,21 @@ void SPICoaxTransceiver::setLoopback(bool loopback)
writeRegister(COAX_REGISTER_CONTROL, loopback ? COAX_REGISTER_CONTROL_LOOPBACK : 0, COAX_REGISTER_CONTROL_LOOPBACK);
}
+void SPICoaxTransceiver::setTXProtocol(CoaxProtocol protocol)
+{
+ writeRegister(COAX_REGISTER_CONTROL, protocol == CoaxProtocol::_3299 ? COAX_REGISTER_CONTROL_TX_PROTOCOL : 0, COAX_REGISTER_CONTROL_TX_PROTOCOL);
+}
+
void SPICoaxTransceiver::setTXParity(CoaxParity parity)
{
writeRegister(COAX_REGISTER_CONTROL, parity == CoaxParity::Even ? COAX_REGISTER_CONTROL_TX_PARITY : 0, COAX_REGISTER_CONTROL_TX_PARITY);
}
+void SPICoaxTransceiver::setRXProtocol(CoaxProtocol protocol)
+{
+ writeRegister(COAX_REGISTER_CONTROL, protocol == CoaxProtocol::_3299 ? COAX_REGISTER_CONTROL_RX_PROTOCOL : 0, COAX_REGISTER_CONTROL_RX_PROTOCOL);
+}
+
void SPICoaxTransceiver::setRXParity(CoaxParity parity)
{
writeRegister(COAX_REGISTER_CONTROL, parity == CoaxParity::Even ? COAX_REGISTER_CONTROL_RX_PARITY : 0, COAX_REGISTER_CONTROL_RX_PARITY);
diff --git a/interface2/firmware/src/interface.cpp b/interface2/firmware/src/interface.cpp
index 5184b1d..926b74f 100644
--- a/interface2/firmware/src/interface.cpp
+++ b/interface2/firmware/src/interface.cpp
@@ -46,6 +46,9 @@ Interface::Interface(Coax &coax, Indicators &indicators) :
_coax(coax),
_indicators(indicators)
{
+ _coax.setTXProtocol(CoaxProtocol::_3270);
+ _coax.setRXProtocol(CoaxProtocol::_3270);
+ _coax.setParity(CoaxParity::Even);
}
void Interface::handleMessage(uint8_t *buffer, size_t bufferCount)
@@ -143,6 +146,12 @@ void Interface::handleTransmitReceive(uint8_t *buffer, size_t bufferCount)
}
}
+ if (transmitBuffer[0] & 0x8000) {
+ _coax.setTXProtocol(CoaxProtocol::_3299);
+ } else {
+ _coax.setTXProtocol(CoaxProtocol::_3270);
+ }
+
int transmitCount = _coax.transmit(transmitBuffer, transmitBufferCount);
if (transmitCount < 0) {
@@ -214,8 +223,9 @@ void Interface::handleInfo(uint8_t *buffer, size_t bufferCount)
buffer[2] = INFO_HARDWARE_TYPE;
buffer[3] = INFO_FIRMWARE_VERSION;
buffer[4] = INFO_MESSAGE_BUFFER_SIZE;
+ buffer[5] = INFO_FEATURES;
- MessageSender::send(buffer, 5);
+ MessageSender::send(buffer, 6);
} else if (query == INFO_HARDWARE_TYPE) {
buffer[0] = 0x01;
@@ -238,6 +248,11 @@ void Interface::handleInfo(uint8_t *buffer, size_t bufferCount)
memcpy(buffer + 1, &size, sizeof(uint32_t));
MessageSender::send(buffer, 5);
+ } else if (query == INFO_FEATURES) {
+ buffer[0] = 0x01;
+ buffer[1] = FEATURE_PROTOCOL_3299;
+
+ MessageSender::send(buffer, 2);
} else {
sendErrorMessage(ERROR_INVALID_MESSAGE, "HANDLE_INFO_UNKNOWN_QUERY");
return;
diff --git a/interface2/firmware/src/main.cpp b/interface2/firmware/src/main.cpp
index 4bcb21d..75e998e 100644
--- a/interface2/firmware/src/main.cpp
+++ b/interface2/firmware/src/main.cpp
@@ -45,7 +45,7 @@ SPICoaxTransceiver spiCoaxTransceiver;
volatile uint16_t coaxBuffer[COAX_BUFFER_SIZE];
-Coax coax(spiCoaxTransceiver, CoaxParity::Even, coaxBuffer, COAX_BUFFER_SIZE);
+Coax coax(spiCoaxTransceiver, coaxBuffer, COAX_BUFFER_SIZE);
volatile uint8_t messageBuffer[MESSAGE_BUFFER_SIZE];
diff --git a/interface2/fpga/rtl/coax_buffered_rx.v b/interface2/fpga/rtl/coax_buffered_rx.v
index 00caac5..ddbd4ab 100644
--- a/interface2/fpga/rtl/coax_buffered_rx.v
+++ b/interface2/fpga/rtl/coax_buffered_rx.v
@@ -24,6 +24,7 @@ module coax_buffered_rx (
input read_strobe,
output empty,
output full,
+ input protocol,
input parity
);
parameter CLOCKS_PER_BIT = 8;
@@ -45,6 +46,7 @@ module coax_buffered_rx (
.error(coax_rx_error),
.data(coax_rx_data),
.strobe(coax_rx_strobe),
+ .protocol(protocol),
.parity(parity)
);
diff --git a/interface2/fpga/rtl/coax_buffered_tx.v b/interface2/fpga/rtl/coax_buffered_tx.v
index aa6ad1f..36bf76d 100644
--- a/interface2/fpga/rtl/coax_buffered_tx.v
+++ b/interface2/fpga/rtl/coax_buffered_tx.v
@@ -25,6 +25,7 @@ module coax_buffered_tx (
output empty,
output full,
output reg ready,
+ input protocol,
input parity
);
parameter CLOCKS_PER_BIT = 8;
@@ -56,6 +57,7 @@ module coax_buffered_tx (
.data(coax_tx_data),
.strobe(coax_tx_strobe),
.ready(coax_tx_ready),
+ .protocol(protocol),
.parity(parity)
);
diff --git a/interface2/fpga/rtl/coax_rx.v b/interface2/fpga/rtl/coax_rx.v
index 7f79e5a..7fbf048 100644
--- a/interface2/fpga/rtl/coax_rx.v
+++ b/interface2/fpga/rtl/coax_rx.v
@@ -22,6 +22,7 @@ module coax_rx (
output reg error,
output reg [9:0] data,
output reg strobe = 0,
+ input protocol,
input parity
);
parameter CLOCKS_PER_BIT = 8;
@@ -95,6 +96,8 @@ module coax_rx (
case (state)
STATE_IDLE:
begin
+ next_input_data = 10'b0;
+
if (ss_detector_strobe)
begin
// The start sequence ends with a code violation, so reset
@@ -111,7 +114,7 @@ module coax_rx (
// differently and consider it part of the start sequence as
// it must be a 1 and we don't consider the receiver active
// until this has been detected.
- next_bit_counter = 0;
+ next_bit_counter = (protocol == 1 ? 4 : 0);
if (rx != previous_rx && mid_bit_counter > CLOCKS_PER_HALF_BIT)
begin
diff --git a/interface2/fpga/rtl/coax_tx.v b/interface2/fpga/rtl/coax_tx.v
index 838a6b1..4afccc6 100644
--- a/interface2/fpga/rtl/coax_tx.v
+++ b/interface2/fpga/rtl/coax_tx.v
@@ -22,6 +22,7 @@ module coax_tx (
input [9:0] data,
input strobe,
output ready,
+ input protocol,
input parity
);
parameter CLOCKS_PER_BIT = 8;
@@ -48,6 +49,8 @@ module coax_tx (
reg next_tx;
+ reg first_word;
+ reg next_first_word;
reg end_sequence;
reg next_end_sequence;
@@ -86,6 +89,7 @@ module coax_tx (
next_tx = 0;
+ next_first_word = first_word;
next_end_sequence = 0;
next_output_data = output_data;
@@ -188,6 +192,7 @@ module coax_tx (
START_SEQUENCE_9:
begin
next_tx = 1;
+ next_first_word = 1;
if (last_clock)
next_state = SYNC_BIT;
@@ -197,10 +202,21 @@ module coax_tx (
begin
next_tx = first_half ? 0 : 1;
- next_bit_counter = 9;
-
if (last_clock)
+ begin
+ // First word is 6 bits in 3299 protocol mode.
+ if (protocol == 1 && first_word)
+ begin
+ next_output_data = { output_data[5:0], 4'b0 };
+ next_bit_counter = 5;
+ end
+ else
+ begin
+ next_bit_counter = 9;
+ end
+
next_state = DATA_BIT;
+ end
end
DATA_BIT:
@@ -228,6 +244,8 @@ module coax_tx (
if (last_clock)
begin
+ next_first_word = 0;
+
if (output_data_full)
begin
next_state = SYNC_BIT;
@@ -280,6 +298,7 @@ module coax_tx (
active <= (state != IDLE); // TODO: this causes active to remain high one additional clock cycle
tx <= next_tx;
+ first_word <= next_first_word;
end_sequence <= next_end_sequence;
output_data <= next_output_data;
diff --git a/interface2/fpga/rtl/control.v b/interface2/fpga/rtl/control.v
index b397eea..2dccfdf 100644
--- a/interface2/fpga/rtl/control.v
+++ b/interface2/fpga/rtl/control.v
@@ -36,6 +36,7 @@ module control (
input tx_empty,
input tx_full,
input tx_ready,
+ output tx_protocol,
output tx_parity,
// RX
@@ -45,6 +46,7 @@ module control (
input [9:0] rx_data,
output reg rx_read_strobe,
input rx_empty,
+ output rx_protocol,
output rx_parity
);
parameter DEFAULT_CONTROL_REGISTER = 8'b01001000;
@@ -342,6 +344,8 @@ module control (
assign loopback = control_register[0];
+ assign tx_protocol = control_register[2];
assign tx_parity = control_register[3];
+ assign rx_protocol = control_register[5];
assign rx_parity = control_register[6];
endmodule
diff --git a/interface2/fpga/rtl/top.v b/interface2/fpga/rtl/top.v
index d9bbd8a..2eed75e 100644
--- a/interface2/fpga/rtl/top.v
+++ b/interface2/fpga/rtl/top.v
@@ -98,6 +98,7 @@ module top (
wire tx_empty;
wire tx_full;
wire tx_ready;
+ wire tx_protocol;
wire tx_parity;
coax_buffered_tx #(
@@ -115,6 +116,7 @@ module top (
.empty(tx_empty),
.full(tx_full),
.ready(tx_ready),
+ .protocol(tx_protocol),
.parity(tx_parity)
);
@@ -125,6 +127,7 @@ module top (
wire [9:0] rx_data;
wire rx_read_strobe;
wire rx_empty;
+ wire rx_protocol;
wire rx_parity;
coax_buffered_rx #(
@@ -139,6 +142,7 @@ module top (
.data(rx_data),
.read_strobe(rx_read_strobe),
.empty(rx_empty),
+ .protocol(rx_protocol),
.parity(rx_parity)
);
@@ -184,6 +188,7 @@ module top (
.tx_empty(tx_empty),
.tx_full(tx_full),
.tx_ready(tx_ready),
+ .tx_protocol(tx_protocol),
.tx_parity(tx_parity),
.rx_reset(rx_reset),
@@ -192,6 +197,7 @@ module top (
.rx_data(rx_data),
.rx_read_strobe(rx_read_strobe),
.rx_empty(rx_empty),
+ .rx_protocol(rx_protocol),
.rx_parity(rx_parity)
);
diff --git a/interface2/fpga/tests/Makefile b/interface2/fpga/tests/Makefile
index 9fea3c4..6fe974d 100644
--- a/interface2/fpga/tests/Makefile
+++ b/interface2/fpga/tests/Makefile
@@ -15,6 +15,7 @@ coax_tx_tb: coax_tx_tb.v $(RTL)/coax_tx.v $(RTL)/coax_tx_bit_timer.v
coax_rx_blanker_tb: coax_rx_blanker_tb.v $(RTL)/coax_rx_blanker.v
coax_tx_rx_frontend_tb: coax_tx_rx_frontend_tb.v $(RTL)/coax_tx_rx_frontend.v $(RTL)/coax_tx_distorter.v $(RTL)/coax_rx_blanker.v
control_tb: control_tb.v $(RTL)/control.v $(RTL)/coax_buffered_tx.v $(RTL)/coax_tx.v $(RTL)/coax_tx_bit_timer.v $(RTL)/coax_buffer.v $(RTL)/third_party/*.v
+tx_rx_loopback_tb: tx_rx_loopback_tb.v $(RTL)/coax_tx.v $(RTL)/coax_tx_bit_timer.v $(RTL)/coax_rx.v $(RTL)/coax_rx_ss_detector.v
regression_memorex_tb: regression_memorex_tb.v $(RTL)/coax_rx.v $(RTL)/coax_rx_ss_detector.v
test: $(TESTS)
diff --git a/interface2/fpga/tests/coax_buffered_rx_tb.v b/interface2/fpga/tests/coax_buffered_rx_tb.v
index 99e0251..778d6e4 100644
--- a/interface2/fpga/tests/coax_buffered_rx_tb.v
+++ b/interface2/fpga/tests/coax_buffered_rx_tb.v
@@ -31,6 +31,7 @@ module coax_buffered_rx_tb;
.reset(reset),
.rx(rx),
.read_strobe(read_strobe),
+ .protocol(1'b0),
.parity(1'b1)
);
diff --git a/interface2/fpga/tests/coax_buffered_tx_tb.v b/interface2/fpga/tests/coax_buffered_tx_tb.v
index f12b758..ad4f458 100644
--- a/interface2/fpga/tests/coax_buffered_tx_tb.v
+++ b/interface2/fpga/tests/coax_buffered_tx_tb.v
@@ -28,6 +28,7 @@ module coax_buffered_tx_tb;
.data(data),
.load_strobe(load_strobe),
.start_strobe(start_strobe),
+ .protocol(1'b0),
.parity(1'b1)
);
diff --git a/interface2/fpga/tests/coax_rx_tb.v b/interface2/fpga/tests/coax_rx_tb.v
index eb68104..90c8059 100644
--- a/interface2/fpga/tests/coax_rx_tb.v
+++ b/interface2/fpga/tests/coax_rx_tb.v
@@ -28,6 +28,7 @@ module coax_rx_tb;
.clk(clk),
.reset(reset),
.rx(rx),
+ .protocol(1'b0),
.parity(1'b1)
);
diff --git a/interface2/fpga/tests/coax_tx_tb.v b/interface2/fpga/tests/coax_tx_tb.v
index fca7f96..4f7b356 100644
--- a/interface2/fpga/tests/coax_tx_tb.v
+++ b/interface2/fpga/tests/coax_tx_tb.v
@@ -24,6 +24,7 @@ module coax_tx_tb;
.reset(reset),
.data(data),
.strobe(strobe),
+ .protocol(1'b0),
.parity(1'b1)
);
diff --git a/interface2/fpga/tests/regression_memorex_tb.v b/interface2/fpga/tests/regression_memorex_tb.v
index 0178506..7c098e4 100644
--- a/interface2/fpga/tests/regression_memorex_tb.v
+++ b/interface2/fpga/tests/regression_memorex_tb.v
@@ -22,6 +22,7 @@ module regression_memorex_tb;
.clk(clk),
.reset(reset),
.rx(rx),
+ .protocol(1'b0),
.parity(1'b1)
);
diff --git a/interface2/fpga/tests/tx_rx_loopback_tb.v b/interface2/fpga/tests/tx_rx_loopback_tb.v
new file mode 100644
index 0000000..89a8dd5
--- /dev/null
+++ b/interface2/fpga/tests/tx_rx_loopback_tb.v
@@ -0,0 +1,323 @@
+`default_nettype none
+
+`include "assert.v"
+
+module tx_rx_loopback_tb;
+ reg clk = 0;
+
+ initial
+ begin
+ forever
+ begin
+ #1 clk <= ~clk;
+ end
+ end
+
+ reg reset = 0;
+ wire loopback;
+ wire tx_active;
+ reg [9:0] tx_data;
+ reg tx_strobe = 0;
+ wire tx_ready;
+ reg tx_protocol = 0;
+ reg tx_parity = 0;
+ wire rx_error;
+ wire [9:0] rx_data;
+ wire rx_strobe;
+ reg rx_protocol = 0;
+ reg rx_parity = 0;
+
+ coax_tx #(
+ .CLOCKS_PER_BIT(8)
+ ) dut_tx (
+ .clk(clk),
+ .reset(reset),
+ .active(tx_active),
+ .tx(loopback),
+ .data(tx_data),
+ .strobe(tx_strobe),
+ .ready(tx_ready),
+ .protocol(tx_protocol),
+ .parity(tx_parity)
+ );
+
+ coax_rx #(
+ .CLOCKS_PER_BIT(8)
+ ) dut_rx (
+ .clk(clk),
+ .reset(reset),
+ .rx(loopback),
+ .error(rx_error),
+ .data(rx_data),
+ .strobe(rx_strobe),
+ .protocol(rx_protocol),
+ .parity(rx_parity)
+ );
+
+ initial
+ begin
+ $dumpfile("tx_rx_loopback_tb.vcd");
+ $dumpvars(0, tx_rx_loopback_tb);
+
+ test_3270_protocol;
+ test_3299_protocol;
+ test_protocol_mismatch;
+ test_parity_mismatch;
+
+ $finish;
+ end
+
+ task test_3270_protocol;
+ begin
+ $display("START: test_3270_protocol");
+
+ tx_protocol = 0;
+ tx_parity = 1;
+ rx_protocol = 0;
+ rx_parity = 1;
+
+ `assert_equal(dut_tx.state, dut_tx.IDLE, "state should be IDLE");
+ `assert_equal(dut_rx.state, dut_rx.STATE_IDLE, "state should be IDLE");
+
+ fork: test_3270_protocol_tx_rx_fork
+ begin
+ tx_data = 10'b0101110101;
+
+ #2;
+ tx_strobe = 1;
+ #2;
+ tx_strobe = 0;
+
+ @(posedge tx_ready);
+
+ tx_data = 10'b1010001010;
+
+ #2;
+ tx_strobe = 1;
+ #2;
+ tx_strobe = 0;
+ end
+
+ begin
+ @(posedge rx_strobe);
+
+ `assert_equal(rx_data, 10'b0101110101, "RX data should be equal to TX data");
+
+ @(posedge rx_strobe);
+
+ `assert_equal(rx_data, 10'b1010001010, "RX data should be equal to TX data");
+
+ disable test_3270_protocol_tx_rx_fork;
+ end
+
+ begin
+ #1000;
+ $display("[TIMEOUT] %m (%s:%0d)", `__FILE__, `__LINE__);
+ disable test_3270_protocol_tx_rx_fork;
+ end
+ join
+
+ #100;
+
+ `assert_equal(dut_tx.state, dut_tx.IDLE, "state should be IDLE");
+ `assert_equal(dut_rx.state, dut_rx.STATE_IDLE, "state should be IDLE");
+
+ $display("END: test_3270_protocol");
+ end
+ endtask
+
+ task test_3299_protocol;
+ begin
+ $display("START: test_3299_protocol");
+
+ tx_protocol = 1;
+ tx_parity = 1;
+ rx_protocol = 1;
+ rx_parity = 1;
+
+ `assert_equal(dut_tx.state, dut_tx.IDLE, "state should be IDLE");
+ `assert_equal(dut_rx.state, dut_rx.STATE_IDLE, "state should be IDLE");
+
+ fork: test_3299_protocol_tx_rx_fork
+ begin
+ tx_data = 10'b0000110001;
+
+ #2;
+ tx_strobe = 1;
+ #2;
+ tx_strobe = 0;
+
+ @(posedge tx_ready);
+
+ tx_data = 10'b0101110101;
+
+ #2;
+ tx_strobe = 1;
+ #2;
+ tx_strobe = 0;
+
+ @(posedge tx_ready);
+
+ tx_data = 10'b1010001010;
+
+ #2;
+ tx_strobe = 1;
+ #2;
+ tx_strobe = 0;
+ end
+
+ begin
+ @(posedge rx_strobe);
+
+ `assert_equal(rx_data, 10'b0000110001, "RX data should be equal to TX data");
+
+ @(posedge rx_strobe);
+
+ `assert_equal(rx_data, 10'b0101110101, "RX data should be equal to TX data");
+
+ @(posedge rx_strobe);
+
+ `assert_equal(rx_data, 10'b1010001010, "RX data should be equal to TX data");
+
+ disable test_3299_protocol_tx_rx_fork;
+ end
+
+ begin
+ #1000;
+ $display("[TIMEOUT] %m (%s:%0d)", `__FILE__, `__LINE__);
+ disable test_3299_protocol_tx_rx_fork;
+ end
+ join
+
+ #100;
+
+ `assert_equal(dut_tx.state, dut_tx.IDLE, "state should be IDLE");
+ `assert_equal(dut_rx.state, dut_rx.STATE_IDLE, "state should be IDLE");
+
+ $display("END: test_3299_protocol");
+ end
+ endtask
+
+ task test_protocol_mismatch;
+ begin
+ $display("START: test_protocol_mismatch");
+
+ tx_protocol = 1;
+ tx_parity = 1;
+ rx_protocol = 0;
+ rx_parity = 1;
+
+ `assert_equal(dut_tx.state, dut_tx.IDLE, "state should be IDLE");
+ `assert_equal(dut_rx.state, dut_rx.STATE_IDLE, "state should be IDLE");
+
+ fork: test_protocol_mismatch_tx_rx_fork
+ begin
+ tx_data = 10'b0000110001;
+
+ #2;
+ tx_strobe = 1;
+ #2;
+ tx_strobe = 0;
+
+ @(posedge tx_ready);
+
+ tx_data = 10'b0101110101;
+
+ #2;
+ tx_strobe = 1;
+ #2;
+ tx_strobe = 0;
+
+ // Wait for TX to complete... we don't want to reset the RX
+ // to soon as it could be reactivated with the remaining
+ // transmission.
+ @(negedge tx_active);
+
+ disable test_protocol_mismatch_tx_rx_fork;
+ end
+
+ begin
+ #1000;
+ $display("[TIMEOUT] %m (%s:%0d)", `__FILE__, `__LINE__);
+ disable test_protocol_mismatch_tx_rx_fork;
+ end
+ join
+
+ // The exact error (parity or loss of mid-bit transition) may depend
+ // on the length of message and data.
+ `assert_high(rx_error, "RX error should be HIGH");
+ `assert_equal(rx_data, dut_rx.ERROR_LOSS_OF_MID_BIT_TRANSITION, "RX data should be ERROR_LOSS_OF_MID_BIT_TRANSITION");
+
+ #16;
+
+ dut_reset;
+
+ #100;
+
+ `assert_equal(dut_tx.state, dut_tx.IDLE, "state should be IDLE");
+ `assert_equal(dut_rx.state, dut_rx.STATE_IDLE, "state should be IDLE");
+
+ $display("END: test_protocol_mismatch");
+ end
+ endtask
+
+ task test_parity_mismatch;
+ begin
+ $display("START: test_parity_mismatch");
+
+ tx_protocol = 0;
+ tx_parity = 1;
+ rx_protocol = 0;
+ rx_parity = 0;
+
+ `assert_equal(dut_tx.state, dut_tx.IDLE, "state should be IDLE");
+ `assert_equal(dut_rx.state, dut_rx.STATE_IDLE, "state should be IDLE");
+
+ fork: test_parity_mismatch_tx_rx_fork
+ begin
+ tx_data = 10'b0101110101;
+
+ #2;
+ tx_strobe = 1;
+ #2;
+ tx_strobe = 0;
+
+ // Wait for TX to complete... we don't want to reset the RX
+ // to soon as it could be reactivated with the remaining
+ // transmission.
+ @(negedge tx_active);
+
+ disable test_parity_mismatch_tx_rx_fork;
+ end
+
+ begin
+ #1000;
+ $display("[TIMEOUT] %m (%s:%0d)", `__FILE__, `__LINE__);
+ disable test_parity_mismatch_tx_rx_fork;
+ end
+ join
+
+ `assert_high(rx_error, "RX error should be HIGH");
+ `assert_equal(rx_data, dut_rx.ERROR_PARITY, "RX data should be ERROR_PARITY");
+
+ #16;
+
+ dut_reset;
+
+ #100;
+
+ `assert_equal(dut_tx.state, dut_tx.IDLE, "state should be IDLE");
+ `assert_equal(dut_rx.state, dut_rx.STATE_IDLE, "state should be IDLE");
+
+ $display("END: test_parity_mismatch");
+ end
+ endtask
+
+ task dut_reset;
+ begin
+ reset = 1;
+ #2;
+ reset = 0;
+ end
+ endtask
+endmodule
diff --git a/protocol/3299.svg b/protocol/3299.svg
new file mode 100644
index 0000000..57a967c
--- /dev/null
+++ b/protocol/3299.svg
@@ -0,0 +1,833 @@
+
+
diff --git a/protocol/protocol.md b/protocol/protocol.md
index 63a749b..9888dfa 100644
--- a/protocol/protocol.md
+++ b/protocol/protocol.md
@@ -54,18 +54,29 @@ calculation includes the sync bit. Words are transmitted most significant bit

+### 3299 Variant
+
+The 3299 variant of the protocol includes a 6-bit address word at the start
+of each frame, specifying the multiplexer port the remainder of the frame
+should be sent to.
+
+
+
+As with 10-bit words, the 6-bit address word starts with a sync bit and ends
+with an even parity bit.
+
+### Words
+
All communication is initiated by the controller when it sends a frame
containing a command word and optional data words. The attached device responds
with a frame containing one or more data words.
-### Words
-
Except for the `POLL` command response, words are either:
* Command - encapsulates a single 8-bit command byte, sent from a controller
to an attached device
* Data - encapsulates a single 8-bit data byte
- * Transmission Turnaround (TR/TA) - sent as an acknowledgment of a command
+ * Transmission Turnaround (TT/AR) - sent as an acknowledgment of a command
when there is no response data
| Bit | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
diff --git a/pycoax/coax/__init__.py b/pycoax/coax/__init__.py
index 30eace8..0b516c0 100644
--- a/pycoax/coax/__init__.py
+++ b/pycoax/coax/__init__.py
@@ -1,5 +1,6 @@
from .__about__ import __version__
+from .interface import InterfaceFeature
from .serial_interface import SerialInterface, open_serial_interface
from .protocol import (
@@ -48,6 +49,8 @@ from .features import (
parse_features
)
+from .multiplexer import get_device_address
+
from .exceptions import (
InterfaceError,
ReceiveError,
diff --git a/pycoax/coax/interface.py b/pycoax/coax/interface.py
index e001094..a885803 100644
--- a/pycoax/coax/interface.py
+++ b/pycoax/coax/interface.py
@@ -10,6 +10,9 @@ from .exceptions import ProtocolError
class Interface:
"""3270 coax interface."""
+ def __init__(self):
+ self.features = set()
+
def reset(self):
"""Reset the interface."""
raise NotImplementedError
@@ -42,6 +45,11 @@ class Interface:
def _transmit_receive(self, outbound_frames, response_lengths, timeout):
raise NotImplementedError
+class InterfaceFeature(Enum):
+ """Interface feature."""
+
+ PROTOCOL_3299 = 0x10
+
class FrameFormat(Enum):
"""3270 coax frame format."""
diff --git a/pycoax/coax/multiplexer.py b/pycoax/coax/multiplexer.py
new file mode 100644
index 0000000..bef8ad7
--- /dev/null
+++ b/pycoax/coax/multiplexer.py
@@ -0,0 +1,22 @@
+"""
+coax.multiplexer
+~~~~~~~~~~~~~~~~
+"""
+
+PORT_MAP_3299 = [
+ # The 3299-2 port numbers appear to be LSB first
+ 0b000000,
+ 0b100000,
+ 0b010000,
+ 0b110000,
+ 0b001000,
+ 0b101000,
+ 0b011000,
+ 0b111000
+]
+
+def get_device_address(port):
+ if port < 0 or port > 7:
+ raise ValueError('Port must be between 0 and 7')
+
+ return PORT_MAP_3299[port]
diff --git a/pycoax/coax/serial_interface.py b/pycoax/coax/serial_interface.py
index 725c2ba..83544aa 100644
--- a/pycoax/coax/serial_interface.py
+++ b/pycoax/coax/serial_interface.py
@@ -11,7 +11,7 @@ from contextlib import contextmanager
from serial import Serial
from sliplib import SlipWrapper, ProtocolError
-from .interface import Interface, FrameFormat
+from .interface import Interface, InterfaceFeature, FrameFormat
from .protocol import pack_data_word
from .exceptions import InterfaceError, InterfaceTimeout, ReceiveError, ReceiveTimeout
@@ -22,6 +22,8 @@ class SerialInterface(Interface):
if serial is None:
raise ValueError('Serial port is required')
+ super().__init__()
+
self.serial = serial
self.slip_serial = SlipSerial(self.serial)
@@ -60,6 +62,13 @@ class SerialInterface(Interface):
else:
raise InterfaceError(f'Invalid reset response: {message}')
+ # Query features, if this is not a legacy firmware.
+ if not self.legacy_firmware_detected:
+ try:
+ self.features = self._get_features()
+ except InterfaceError:
+ pass
+
def enter_dfu_mode(self):
"""Enter device firmware upgrade mode."""
message = bytes([0xf2])
@@ -71,18 +80,35 @@ class SerialInterface(Interface):
if message[0] != 0x01:
raise _convert_error(message)
- def _transmit_receive(self, outbound_frames, response_lengths, timeout):
- if any(address is not None for (address, _) in outbound_frames):
- raise NotImplementedError('Interface does not support 3299 protocol')
+ def _get_features(self):
+ """Get interface features."""
+ message = bytes([0xf0, 0x07])
+ self._write_message(message)
+
+ message = self._read_message()
+
+ if message[0] != 0x01:
+ return _convert_error(message)
+
+ known_feature_values = {feature.value for feature in InterfaceFeature}
+
+ features = {InterfaceFeature(value) for value in message[1:] if value in known_feature_values}
+
+ return features
+
+ def _transmit_receive(self, outbound_frames, response_lengths, timeout):
if len(response_lengths) != len(outbound_frames):
raise ValueError('Response lengths length must equal outbound frames length')
+ if any(address is not None for (address, _) in outbound_frames) and InterfaceFeature.PROTOCOL_3299 not in self.features:
+ raise NotImplementedError('Interface does not support 3299 protocol')
+
# Pack all messages before sending.
timeout_milliseconds = self._calculate_timeout_milliseconds(timeout)
- messages = [_pack_transmit_receive_message(frame, response_length, timeout_milliseconds)
- for ((_, frame), response_length) in zip(outbound_frames, response_lengths)]
+ messages = [_pack_transmit_receive_message(address, frame, response_length, timeout_milliseconds)
+ for ((address, frame), response_length) in zip(outbound_frames, response_lengths)]
responses = []
@@ -158,7 +184,7 @@ def open_serial_interface(serial_port, reset=True):
yield interface
-def _pack_transmit_receive_message(frame, response_length, timeout_milliseconds):
+def _pack_transmit_receive_message(address, frame, response_length, timeout_milliseconds):
message = bytes([0x06])
repeat_count = 0
@@ -197,6 +223,15 @@ def _pack_transmit_receive_message(frame, response_length, timeout_milliseconds)
for byte in frame[1]:
bytes_ += struct.pack(' 63:
+ raise ValueError('Address must be between 0 and 63')
+
+ if repeat_count > 0:
+ repeat_offset += 1
+
+ bytes_ = struct.pack('H', (repeat_offset << 15) | repeat_count)
message += bytes_
message += struct.pack('>HH', response_length, timeout_milliseconds)
diff --git a/pycoax/tests/test_serial_interface.py b/pycoax/tests/test_serial_interface.py
index 20d7c0a..278c3f9 100644
--- a/pycoax/tests/test_serial_interface.py
+++ b/pycoax/tests/test_serial_interface.py
@@ -5,7 +5,7 @@ import sliplib
import context
-from coax.interface import FrameFormat
+from coax.interface import InterfaceFeature, FrameFormat
from coax.serial_interface import SerialInterface
from coax.exceptions import InterfaceError, ReceiveTimeout
@@ -25,7 +25,7 @@ class SerialInterfaceResetTestCase(unittest.TestCase):
self.interface.reset()
# Assert
- self.interface._write_message.assert_called_with(bytes.fromhex('01'))
+ self.interface._write_message.assert_any_call(bytes.fromhex('01'))
def test_non_legacy_response_is_handled_correctly(self):
# Act
@@ -191,9 +191,27 @@ class SerialInterfaceTransmitReceiveTestCase(unittest.TestCase):
# Assert
self.interface._write_message.assert_called_with(bytes.fromhex('06 00 00 ff 03 02 00 fe 03 00 01 01 f4'))
- def test_addressed_frame(self):
+ def test_addressed_frame_with_3299_protocol_feature(self):
+ # Arrange
+ self.interface.features.add(InterfaceFeature.PROTOCOL_3299)
+
+ self.interface._read_message.return_value=bytes.fromhex('01 00 00')
+
+ # Act
+ responses = self.interface._transmit_receive([(0b111000, (FrameFormat.WORDS, [0b1111111111, 0b0000000000]))], [1], None)
+
+ # Assert
+ self.assertEqual(responses, [[0]])
+
+ self.interface._write_message.assert_called_with(bytes.fromhex('06 00 00 38 80 ff 03 00 00 00 01 00 00'))
+
+ def test_addressed_frame_with_no_3299_protocol_feature(self):
+ # Arrange
+ self.interface._read_message.return_value=bytes.fromhex('01 00 00')
+
+ # Act and assert
with self.assertRaises(NotImplementedError):
- self.interface._transmit_receive([(0b111000, (FrameFormat.WORD_DATA, 0b1111111111, [0x00, 0xff]))], [1], 0.5)
+ self.interface._transmit_receive([(0b111000, (FrameFormat.WORDS, [0b1111111111, 0b0000000000]))], [1], None)
def test_multiple_frames(self):
# Arrange