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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SyncBit + ParityBit + Address (6-bits) + + + + SyncBit + ParityBit + Data (10-bits) + + + + 1 + + 1 + + 0 + + 0 + + 0 + + 0 + + 0 + + 0 + + 1 + + 1 + + 0 + + + 0 + + + 0 + + 1 + + 1 + 0 + + 0 + + 1 + + 1 + + 0 + + + + Additional words, ifany + + + 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 ![Diagram showing two 10-bit words within a 3270 protocol frame](data.svg) +### 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. + +![Diagram showing a 3299 protocol frame with 6-bit address word](3299.svg) + +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