diff --git a/src/backend_uart/doc/logic.md b/src/backend_uart/doc/logic.md index 32b3ceb..7eb9403 100644 --- a/src/backend_uart/doc/logic.md +++ b/src/backend_uart/doc/logic.md @@ -33,4 +33,4 @@ The following resource utilization of the hardware logic was measured on a Xilinx 7 Series device: | LUTs | Registers | BRAMs | -| 180 | 185 | 1 | +| 323 | 154 | 2 | diff --git a/src/backend_uart/doc/overview.md b/src/backend_uart/doc/overview.md index bf6c763..daf3edc 100644 --- a/src/backend_uart/doc/overview.md +++ b/src/backend_uart/doc/overview.md @@ -13,7 +13,8 @@ Supported Features ================== - Number of channels: 1 (fixed) - FIFO width: 8 bit (fixed) - +- Logic reset +- Robust end-to-end flow control, even without CTS/RTS Components ========== @@ -24,7 +25,7 @@ For details and usage instructions for the individual components, see these pages: - @ref backend_uart-sw "libglip Backend" - @ref backend_uart-logic "FPGA Logic" - +- @ref backend_uart-protocol "Details of the end-to-end flow control" Performance ========== @@ -33,4 +34,9 @@ As UART is a relatively slow interface, the backend usually reaches the maximum throughput. That is 10 symbols (8 bit payload plus start and stop bit), meaning BAUD/10 symbols Byte/s. For example, With 115,200 Baud you get 11,520 Byte/s and with 3,000,000 Baud you get -300,000 Byte/s. +300,000 Byte/s. There is a small loss of this theoretical throughput +as we need some flow control messages and mask the byte `0xfe` in the +data stream. With uniformly distributed random data you get +approximately 298,500 Byte/s at 3 MBaud. Hence you generally get +nearly full throughput, with the exception that it drops to roughly +50% if you only send the byte `0xfe`. diff --git a/src/backend_uart/doc/protocol.md b/src/backend_uart/doc/protocol.md new file mode 100644 index 0000000..7c76d34 --- /dev/null +++ b/src/backend_uart/doc/protocol.md @@ -0,0 +1,84 @@ +@defgroup backend_uart-protocol UART Protocol +@ingroup backend_uart + +Standard UART does not have any out-of-band signaling, meaning we +cannot use control signals. The glip interface requires a reset signal +to reset the logic without board interaction. This signal needs to be +controlled by in-band signaling. + +In-Band Signaling +----------------- + +UART is one byte per transfer and we obviously want to use the full +byte in the default case. Hence one word is defined as a special +word. When this word needs to be transmitted, it needs to be +transferred twice. If the second word is not the same, that is a +control message. We use `0xfe` as `0x00` and `0xff` along with lower +numbers are safely assumed to be in the stream at a higher rate. + +Example user data stream: + + 0xaf 0xfe 0x00 0xff + +UART data stream: + + 0xaf 0xfe 0xfe 0x00 0xff + +Credit-based Flow Control +------------------------- + +The reset can happen at anytime, and especially when the input FIFO is +full as the user logic has deadlocked or similar. The problem now is +how to transfer a control word into the device when the input is +blocked. Hence, we need credit-based flow control, where the host gets +clearance from the device to send a certain amount of data. With each +transfer the host loses one of the credits and it needs to wait for +new credit from the device. This credit also needs to be transfered +in-band, meaning that we apply the same in-band signaling scheme +described above. + +One may think it is safe to not implement flow control in the other +direction (logic->host), but to have a robust it is also required to +implement credit-based flow control there. In glip we don't make any +assumptions about when data is read and how input and output data +relate. Hence the back-pressure from the input may block the credit +messages and a deadlock in the user code may occur. + +Summarizing, we need credit-based flow control in both directions. + +Protocol Datagrams +------------------ + +From logic to host we have the following protocol datagrams: + +| word 0 | word 1 | Description | +|--------------------|---------------|---------------| +| `{credit[14:8],1}` | `credit[7:0]` | Credit update | + +From host to logic we have the following protocol datagrams: + +| word 0 | word 1 | Description | +|----------------------|---------------|--------------------------| +| `{0,credit[13:8],1}` | `credit[7:0]` | Credit update | +| `100000r1` | - | Set logic rst pin to `r` | +| `100001r1` | - | Set comm rst pin to `r` | + +The credit datagrams are exchanged as follows: + +For the *ingress* path (host to logic), the credit is sent right after +reset of the communication. This reset is usually a board reset, but +most importantly when connecting from the host. The communication +controller then observes the data stream and updates the credit each +time it falls below the 50% threshold. This gives the host sufficient +time to actually receive this message before running out of credits, +and on the other hand does not imply too many messages. + +For the *egress* path (logic to host), the host gives all initial +credit in a few tranches to the FPGA. It then also observes the +incoming data and sends a new credit of the maximum number that can be +transferred in one datagram (`0x3fff`) once the remaining credit falls +below the threshold of `HOST_BUFFER_SIZE - 0x3fff`. + +The rst pin is set on demand by the user application. The +communications reset pin is used by the communication controller to +reset itself to a defined state. diff --git a/src/backend_uart/logic/demo/nexys4ddr/nexys4ddr.v b/src/backend_uart/logic/demo/nexys4ddr/nexys4ddr.v index 0373dda..ab11136 100644 --- a/src/backend_uart/logic/demo/nexys4ddr/nexys4ddr.v +++ b/src/backend_uart/logic/demo/nexys4ddr/nexys4ddr.v @@ -104,6 +104,8 @@ module nexys4ddr end end + wire logic_rst; + glip_uart_toplevel #(.FREQ(FREQ), .BAUD(BAUD)) @@ -115,6 +117,8 @@ module nexys4ddr .uart_cts (uart_cts), .uart_rts (uart_rts), .error (error), + .logic_rst (logic_rst), + .com_rst (com_rst), .fifo_in_data (in_data), .fifo_in_valid (in_valid), .fifo_in_ready (in_ready), @@ -128,7 +132,7 @@ module nexys4ddr glip_measure_sevensegment #(.FREQ(FREQ), .DIGITS(8), .OFFSET(0), .STEP(4'd1)) u_measure(.clk (clk), - .rst (rst), + .rst (logic_rst), .trigger ((in_valid & in_ready) | (out_valid & out_ready)), .digits (digits), .overflow (overflow)); @@ -136,7 +140,7 @@ module nexys4ddr nexys4ddr_display #(.FREQ(FREQ)) u_display(.clk (clk), - .rst (rst), + .rst (logic_rst), .digits (digits), .decpoints (8'b00001000), .CA (CA), diff --git a/src/backend_uart/logic/demo/nexys4ddr/vivado.tcl b/src/backend_uart/logic/demo/nexys4ddr/vivado.tcl index 9a20c70..a9471a2 100644 --- a/src/backend_uart/logic/demo/nexys4ddr/vivado.tcl +++ b/src/backend_uart/logic/demo/nexys4ddr/vivado.tcl @@ -92,8 +92,13 @@ set files [list \ "[file normalize "$origin_dir/nexys4ddr.v"]"\ "[file normalize "$origin_dir/../../../../common/logic/nexys4ddr/nexys4ddr_display.v"]"\ "[file normalize "$origin_dir/../../verilog/glip_uart_toplevel.v"]"\ + "[file normalize "$origin_dir/../../verilog/glip_uart_control.v"]"\ + "[file normalize "$origin_dir/../../verilog/glip_uart_control_egress.v"]"\ + "[file normalize "$origin_dir/../../verilog/glip_uart_control_ingress.v"]"\ "[file normalize "$origin_dir/../../verilog/glip_uart_receive.v"]"\ "[file normalize "$origin_dir/../../verilog/glip_uart_transmit.v"]"\ + "[file normalize "$origin_dir/../../../../common/logic/credit/verilog/creditor.v"]"\ + "[file normalize "$origin_dir/../../../../common/logic/credit/verilog/debtor.v"]"\ "[file normalize "$origin_dir/../../../../common/logic/bcdcounter/bcdcounter.v"]"\ "[file normalize "$origin_dir/../../../../common/logic/sevensegment/sevensegment.v"]"\ "[file normalize "$origin_dir/../../../../common/logic/measure/glip_measure_sevensegment.v"]"\ diff --git a/src/backend_uart/logic/verilog/glip_uart_control.v b/src/backend_uart/logic/verilog/glip_uart_control.v new file mode 100644 index 0000000..e8af21b --- /dev/null +++ b/src/backend_uart/logic/verilog/glip_uart_control.v @@ -0,0 +1,285 @@ +/* Copyright (c) 2016 by the author(s) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * ============================================================================= + * + * Control layer between interface and the FIFOs that handles control messages + * like credits etc. + * + * Author(s): + * Stefan Wallentowitz + */ + +module glip_uart_control + #(parameter FIFO_CREDIT_WIDTH = 1'bx, + parameter INPUT_FIFO_CREDIT = 1'bx, + parameter FREQ = 1'bx) + ( + input clk, + input rst, + + input [7:0] ingress_in_data, + input ingress_in_valid, + output ingress_in_ready, + + output [7:0] ingress_out_data, + output ingress_out_valid, + input ingress_out_ready, + + input [7:0] egress_in_data, + input egress_in_valid, + output egress_in_ready, + + output [7:0] egress_out_data, + output egress_out_enable, + input egress_out_done, + + input transfer_in, + + output reg logic_rst, + output reg com_rst, + output error + ); + + // Submodule errors + wire [3:0] mod_error; + + // Transfer on egress path + wire transfer_egress; + + // Sufficient credit + wire can_send; + + // Debt we get from the host + wire [13:0] debt; + wire debt_en; + + // Credit we send to the host + wire [FIFO_CREDIT_WIDTH-1:0] credit; + reg credit_en; + wire credit_ack; + reg get_credit; + wire get_credit_ack; + + // Reset registers + wire logic_rst_en; + wire logic_rst_val; + wire com_rst_en; + wire com_rst_val; + + // Collect submodule errors as control error + assign error = |mod_error; + + // Count the ingress transfers to know when we can give new credit + reg [FIFO_CREDIT_WIDTH-1:0] transfer_counter; + reg [FIFO_CREDIT_WIDTH-1:0] nxt_transfer_counter; + + // We want to send new credit + reg send_credit_pnd; + reg nxt_send_credit_pnd; + // Start a request to the creditor module. It can be one cycle + // delayed, hence make it a register + reg nxt_get_credit; + + // Sequential part of credit generation logic + always @(posedge clk) begin + if (rst | com_rst) begin + transfer_counter <= 0; + send_credit_pnd <= 0; + get_credit <= 0; + end else begin + transfer_counter <= nxt_transfer_counter; + send_credit_pnd <= nxt_send_credit_pnd; + get_credit <= nxt_get_credit; + end + end + + // Combinational part of credit generation logic + always @(*) begin + nxt_send_credit_pnd = send_credit_pnd; + nxt_get_credit = get_credit; + nxt_transfer_counter = transfer_counter; + + credit_en = 0; + nxt_get_credit = get_credit; + + if (send_credit_pnd) begin + // We are in the process of sending new credit + if (get_credit) begin + // .. and still wait for the creditor module to update + if (get_credit_ack) begin + // done + nxt_get_credit = 0; + credit_en = 1; + end + end else begin + // We have a new credit, send it + credit_en = 1; + if (credit_ack) begin + // completed + nxt_send_credit_pnd = 0; + end + end // else: !if(get_credit) + + // If a transfer happens in between capture it. There should + // never occur more than one transfer actually + if (transfer_counter != 0) begin + if (transfer_in) begin + nxt_transfer_counter = transfer_counter - 1; + end + end + end else begin // if (send_credit_pnd) + // We are not in the process of sending a credit + if (transfer_counter == 0) begin + // But if we have counted down, we reset the counter to + // the 1/2 threshold and go into sending the credit + nxt_transfer_counter = INPUT_FIFO_CREDIT >> 1; + nxt_send_credit_pnd = 1; + nxt_get_credit = 1; + end else if (transfer_in) begin + // Count transfer in this cycle. We cannot miss a transfer + // from above as there are always many cycles (at least + // 10) between transfers + nxt_transfer_counter = nxt_transfer_counter - 1; + end + end + end + + // Control the reset registers + always @(posedge clk) begin + if (rst) begin + logic_rst <= 0; + com_rst <= 0; + end else begin + if (logic_rst_en) begin + logic_rst <= logic_rst_val; + end + if (com_rst_en) begin + com_rst <= com_rst_val; + end + end + end + + /* debtor AUTO_TEMPLATE( + .rst (com_rst), + .owing (can_send), + .error (mod_error[3]), + .payback (transfer_egress), + .tranche (debt), + .lend (debt_en), + ); */ + debtor + #(.WIDTH(15), .TRANCHE_WIDTH(14)) + u_debtor(/*AUTOINST*/ + // Outputs + .owing (can_send), // Templated + .error (mod_error[3]), // Templated + // Inputs + .clk (clk), + .rst (com_rst), // Templated + .payback (transfer_egress), // Templated + .tranche (debt), // Templated + .lend (debt_en)); // Templated + + /* creditor AUTO_TEMPLATE( + .rst (com_rst), + .payback (transfer_in), + .borrow (get_credit), + .grant (get_credit_ack), + .error (mod_error[2]), + .credit (credit[FIFO_CREDIT_WIDTH-1:0]), + ); */ + creditor + #(.WIDTH(15), .CREDIT_WIDTH(FIFO_CREDIT_WIDTH), + .INITIAL_VALUE(INPUT_FIFO_CREDIT)) + u_creditor(/*AUTOINST*/ + // Outputs + .credit (credit[FIFO_CREDIT_WIDTH-1:0]), // Templated + .grant (get_credit_ack), // Templated + .error (mod_error[2]), // Templated + // Inputs + .clk (clk), + .rst (com_rst), // Templated + .payback (transfer_in), // Templated + .borrow (get_credit)); // Templated + + /* glip_uart_control_egress AUTO_TEMPLATE( + .rst (com_rst), + .in_\(.*\) (egress_in_\1), + .out_\(.*\) (egress_out_\1), + .transfer (transfer_egress), + .error (mod_error[1]), + .credit ({{15-FIFO_CREDIT_WIDTH{1'b0}},credit}), + ); */ + glip_uart_control_egress + u_egress(/*AUTOINST*/ + // Outputs + .in_ready (egress_in_ready), // Templated + .out_data (egress_out_data), // Templated + .out_enable (egress_out_enable), // Templated + .transfer (transfer_egress), // Templated + .credit_ack (credit_ack), + .error (mod_error[1]), // Templated + // Inputs + .clk (clk), + .rst (com_rst), // Templated + .in_data (egress_in_data), // Templated + .in_valid (egress_in_valid), // Templated + .out_done (egress_out_done), // Templated + .can_send (can_send), + .credit ({{15-FIFO_CREDIT_WIDTH{1'b0}},credit}), // Templated + .credit_en (credit_en)); + + /* glip_uart_control_ingress AUTO_TEMPLATE( + .in_\(.*\) (ingress_in_\1), + .out_\(.*\) (ingress_out_\1), + .credit_val (debt), + .credit_en (debt_en), + .transfer (), + .error (mod_error[0]), + .rst_en (ctrl_rst_en), + .rst_val (ctrl_rst_val), + ); */ + glip_uart_control_ingress + u_ingress(/*AUTOINST*/ + // Outputs + .in_ready (ingress_in_ready), // Templated + .out_data (ingress_out_data), // Templated + .out_valid (ingress_out_valid), // Templated + .transfer (), // Templated + .credit_en (debt_en), // Templated + .credit_val (debt), // Templated + .logic_rst_en (logic_rst_en), + .logic_rst_val (logic_rst_val), + .com_rst_en (com_rst_en), + .com_rst_val (com_rst_val), + .error (mod_error[0]), // Templated + // Inputs + .clk (clk), + .rst (rst), + .in_data (ingress_in_data), // Templated + .in_valid (ingress_in_valid), // Templated + .out_ready (ingress_out_ready)); // Templated + +endmodule + +// Local Variables: +// verilog-library-directories:("." "../../../common/logic/credit/verilog") +// End: diff --git a/src/backend_uart/logic/verilog/glip_uart_control_egress.v b/src/backend_uart/logic/verilog/glip_uart_control_egress.v new file mode 100644 index 0000000..15d2981 --- /dev/null +++ b/src/backend_uart/logic/verilog/glip_uart_control_egress.v @@ -0,0 +1,160 @@ +/* Copyright (c) 2016 by the author(s) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * ============================================================================= + * + * Egress path. Multiplexes the credit messages into the data stream. + * + * Author(s): + * Stefan Wallentowitz + */ + +module glip_uart_control_egress + ( + input clk, + input rst, + + // FIFO interface input + input [7:0] in_data, + input in_valid, + output reg in_ready, + + // Interface to transmit module + output reg [7:0] out_data, + output reg out_enable, + input out_done, + + // Sufficient credit to send data + input can_send, + + // A transfer is completed + output transfer, + + // Request to send a credit + input [14:0] credit, + input credit_en, + output reg credit_ack, + + // Error case + output reg error + ); + + // Only user transfers are counted + assign transfer = in_valid & in_ready; + + localparam STATE_IDLE = 0; + localparam STATE_PASSTHROUGH = 1; + localparam STATE_PASSTHROUGH_REPEAT = 2; + localparam STATE_SENDCREDIT1 = 3; + localparam STATE_SENDCREDIT2 = 4; + localparam STATE_SENDCREDIT3 = 5; + + reg [2:0] state; + reg [2:0] nxt_state; + + // Sequential part of state machine + always @(posedge clk) begin + if (rst) begin + state <= STATE_PASSTHROUGH; + end else begin + state <= nxt_state; + end + end + + // Combinational part of state machine + always @(*) begin + // Registers + nxt_state = state; + + // Default values + in_ready = 1'b0; + out_data = 8'hx; + out_enable = 1'b0; + + credit_ack = 1'b0; + + error = 0; + + case (state) + STATE_IDLE: begin + if (credit_en) begin + // Start credit request + nxt_state = STATE_SENDCREDIT1; + end else if (can_send & in_valid) begin + // Tranfer a user word + nxt_state = STATE_PASSTHROUGH; + end + end + STATE_PASSTHROUGH: begin + // Pass-through data and start transfer + out_data = in_data; + out_enable = 1'b1; + if (out_done) begin + // In this cycle the transfer completed + // Acknowledge + in_ready = 1'b1; + if (in_data == 8'hfe) begin + // 0xfe needs to be replicated + nxt_state = STATE_PASSTHROUGH_REPEAT; + end else begin + // Accept new word or send credit + nxt_state = STATE_IDLE; + end + end + end + STATE_PASSTHROUGH_REPEAT: begin + // Plainly repeat the item + out_data = 8'hfe; + out_enable = can_send; + if (out_done) begin + nxt_state = STATE_IDLE; + end + end + STATE_SENDCREDIT1: begin + // Send control indicator first + out_data = 8'hfe; + out_enable = 1; + if (out_done) begin + nxt_state = STATE_SENDCREDIT2; + end + end + STATE_SENDCREDIT2: begin + // bit 0 differs from 0xfe, upper 7 bits are payload + out_data = { credit[14:8], 1'b1 }; + out_enable = 1; + if (out_done) begin + nxt_state = STATE_SENDCREDIT3; + end + end + STATE_SENDCREDIT3: begin + // last word with the remain of the credit + out_data = { credit[7:0] }; + out_enable = 1; + if (out_done) begin + nxt_state = STATE_IDLE; + credit_ack = 1'b1; + end + end + endcase + end +endmodule // glip_uart_control_egress + + + diff --git a/src/backend_uart/logic/verilog/glip_uart_control_ingress.v b/src/backend_uart/logic/verilog/glip_uart_control_ingress.v new file mode 100644 index 0000000..a5be23f --- /dev/null +++ b/src/backend_uart/logic/verilog/glip_uart_control_ingress.v @@ -0,0 +1,172 @@ +/* Copyright (c) 2016 by the author(s) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * ============================================================================= + * + * Ingress path. Filters control messages from the stream. + * + * Author(s): + * Stefan Wallentowitz + */ + +module glip_uart_control_ingress + ( + input clk, + input rst, + + // Both FIFO interfaces + input [7:0] in_data, + input in_valid, + output in_ready, + output [7:0] out_data, + output reg out_valid, + input out_ready, + + // Count transfers for credits + output transfer, + + // Credit control message detected + output reg credit_en, + output reg [13:0] credit_val, + + // Logic reset control message detected + output reg logic_rst_en, + output reg logic_rst_val, + + // Communication reset control message detected + output reg com_rst_en, + output reg com_rst_val, + + // Error case + output error + ); + + // Just bypass data, the out_enable filters + assign out_data = in_data; + + // User transfers count only + assign transfer = out_valid & out_ready; + + // We are always ready. Due to the credit-based flow control there + // shall never be backpressure here. + assign in_ready = 1'b1; + + // An FSM error or backpressure are error cases + reg fsm_error; + assign error = ~out_ready | fsm_error; + + localparam STATE_PASSTHROUGH = 0; + localparam STATE_MATCH = 1; + localparam STATE_CREDIT = 2; + + reg [1:0] state; + reg [1:0] nxt_state; + reg [5:0] credit_first; + reg [5:0] nxt_credit_first; + + // Sequential part of state machine + always @(posedge clk) begin + if (rst) begin + state <= STATE_PASSTHROUGH; + end else begin + state <= nxt_state; + end + // This is never used in the first cycle after reset. Can use + // any reset value + credit_first <= nxt_credit_first; + end + + // Data matches the marker + wire in_data_match = (in_data == 8'hfe); + + always @(*) begin + // Register defaults + nxt_state = state; + nxt_credit_first = credit_first; + + // Default outputs + out_valid = 1'b0; + fsm_error = 1'b0; + logic_rst_en = 1'b0; + com_rst_en = 1'b0; + logic_rst_val = 1'b0; + com_rst_val = 1'b0; + credit_en = 1'b0; + credit_val = 14'hx; + + case(state) + STATE_PASSTHROUGH: begin + if (in_valid) begin + // Input data received + if (in_data_match) begin + // We match the filter condition, filter + nxt_state = STATE_MATCH; + end else begin + // Otherwise just forward data + out_valid = 1'b1; + end + end + end + STATE_MATCH: begin + if (in_valid) begin + // Next data item + if (~in_data[0]) begin + // If bit 0 is not set this is the repeated data item + // Verify that this is true and continue data stream + fsm_error = !in_data_match; + nxt_state = STATE_PASSTHROUGH; + out_valid = 1'b1; + end else if (~in_data[7]) begin + // The first bit is set for credit messages Store the + // data in the register to assemble the full length + // after we received the next word + nxt_credit_first = in_data[6:1]; + nxt_state = STATE_CREDIT; + end else begin + // The first bit is not set for other control + // messages. Currently we only have the rst control + // message. Bit 1 is the new state of the rst + // register + logic_rst_en = ~in_data[2]; + com_rst_en = in_data[2]; + logic_rst_val = in_data[1]; + com_rst_val = in_data[1]; + + // Go back to data stream bypassing + nxt_state = STATE_PASSTHROUGH; + end + end + end // case: STATE_MATCH + STATE_CREDIT: begin + if (in_valid) begin + // This is the extra word for the credit. Emit this and + // the previously saved higher bits + credit_val = {credit_first, in_data}; + credit_en = 1'b1; + // Go back to data stream bypassing + nxt_state = STATE_PASSTHROUGH; + end + end + endcase // case (state) + end +endmodule // glip_uart_control_egress + + + diff --git a/src/backend_uart/logic/verilog/glip_uart_toplevel.v b/src/backend_uart/logic/verilog/glip_uart_toplevel.v index 6b0a6d0..f441498 100644 --- a/src/backend_uart/logic/verilog/glip_uart_toplevel.v +++ b/src/backend_uart/logic/verilog/glip_uart_toplevel.v @@ -27,7 +27,7 @@ * bit, no parity and one stop bit. All baud rates are supported, but * be careful with low frequencies and large baud rates that the * tolerance of the rounded bit divisor (rounding error of - * FREQ/BAUD) is withint 2%. + * FREQ/BAUD) is within 2%. * * Parameters: * - FREQ: The frequency of the design, to match the second @@ -56,61 +56,109 @@ module glip_uart_toplevel output fifo_in_valid, input fifo_in_ready, + // GLIP Control Interface + output logic_rst, + output com_rst, + // UART Interface input uart_rx, output uart_tx, input uart_cts, output uart_rts, - + // Error signal if failure on the line output reg error ); - wire [7:0] in_data; - wire in_valid; - wire in_ready; - wire [7:0] out_data; - wire out_valid; - wire out_ready; + wire [7:0] ingress_in_data; + wire ingress_in_valid; + wire ingress_in_ready; + wire [7:0] ingress_out_data; + wire ingress_out_valid; + wire ingress_out_ready; + wire [7:0] ingress_buffer_data; + wire ingress_buffer_valid; + wire ingress_buffer_ready; + wire [7:0] egress_in_data; + wire egress_in_valid; + wire egress_in_ready; + wire [7:0] egress_out_data; + wire egress_out_enable; + wire egress_out_done; + + wire transfer_in; + assign transfer_in = ingress_buffer_valid & ingress_buffer_ready; // Map FIFO signals to flow control - wire in_fifo_almostfull; + wire in_fifo_full; wire in_fifo_empty; + wire in_buffer_almost_full; + wire in_buffer_empty; wire out_fifo_full; wire out_fifo_empty; - assign in_ready = ~in_almost_full; + assign ingress_out_ready = ~in_buffer_almost_full; + assign ingress_buffer_valid = ~in_buffer_empty; + assign ingress_buffer_ready = ~in_fifo_full; assign fifo_in_valid = ~in_fifo_empty; - assign out_valid = ~out_fifo_empty; + assign egress_in_valid = ~out_fifo_empty; assign fifo_out_ready = ~out_fifo_full; - // Ready to receive if FIFO has a few places left and no error - // happened so far - assign uart_rts = ~(in_ready & ~error); + assign uart_rts = 0; - // Generate error. Sticky when an error occured. Currently we only - // have receiver errors if the frame was incorrect + // Generate error. Sticky when an error occured. wire rcv_error; + wire control_error; always @(posedge clk_io) begin - if (rst) begin + if (rst | com_rst) begin error <= 0; end else begin - error <= error | rcv_error; + error <= error | rcv_error | control_error; end end + + /* glip_uart_control AUTO_TEMPLATE( + .clk (clk_io), + .error (control_error), + ); */ + glip_uart_control + #(.FIFO_CREDIT_WIDTH(12), + .INPUT_FIFO_CREDIT(4090), + .FREQ(FREQ)) + u_control(/*AUTOINST*/ + // Outputs + .ingress_in_ready (ingress_in_ready), + .ingress_out_data (ingress_out_data[7:0]), + .ingress_out_valid (ingress_out_valid), + .egress_in_ready (egress_in_ready), + .egress_out_data (egress_out_data[7:0]), + .egress_out_enable (egress_out_enable), + .logic_rst (logic_rst), + .com_rst (com_rst), + .error (control_error), // Templated + // Inputs + .clk (clk_io), // Templated + .rst (rst), + .ingress_in_data (ingress_in_data[7:0]), + .ingress_in_valid (ingress_in_valid), + .ingress_out_ready (ingress_out_ready), + .egress_in_data (egress_in_data[7:0]), + .egress_in_valid (egress_in_valid), + .egress_out_done (egress_out_done), + .transfer_in (transfer_in)); /* glip_uart_receive AUTO_TEMPLATE( .clk (clk_io), .rx (uart_rx), - .enable (in_valid), - .data (in_data), + .enable (ingress_in_valid), + .data (ingress_in_data), .error (rcv_error), ); */ glip_uart_receive #(.DIVISOR(FREQ/BAUD)) u_receive(/*AUTOINST*/ // Outputs - .enable (in_valid), // Templated - .data (in_data), // Templated + .enable (ingress_in_valid), // Templated + .data (ingress_in_data), // Templated .error (rcv_error), // Templated // Inputs .clk (clk_io), // Templated @@ -118,23 +166,51 @@ module glip_uart_toplevel .rx (uart_rx)); // Templated /* glip_uart_transmit AUTO_TEMPLATE( + .rst (com_rst), .clk (clk_io), .tx (uart_tx), - .done (out_ready), - .enable (out_valid & ~uart_cts), - .data (out_data[]), + .done (egress_out_done), + .enable (egress_out_enable & ~uart_cts), + .data (egress_out_data[]), ); */ glip_uart_transmit #(.DIVISOR(FREQ/BAUD)) u_transmit(/*AUTOINST*/ // Outputs .tx (uart_tx), // Templated - .done (out_ready), // Templated + .done (egress_out_done), // Templated // Inputs .clk (clk_io), // Templated - .rst (rst), - .data (out_data[7:0]), // Templated - .enable (out_valid & ~uart_cts)); // Templated + .rst (com_rst), // Templated + .data (egress_out_data[7:0]), // Templated + .enable (egress_out_enable & ~uart_cts)); // Templated + + // Buffer uart -> logic + FIFO_DUALCLOCK_MACRO + #(.ALMOST_FULL_OFFSET(9'h006), // Sets almost full threshold + .DATA_WIDTH(8), // Valid values are 1-72 (37-72 only valid when FIFO_SIZE="36Kb") + .DEVICE(XILINX_TARGET_DEVICE), // Target device: "VIRTEX5", "VIRTEX6", "7SERIES" + .FIFO_SIZE("36Kb"), // Target BRAM: "18Kb" or "36Kb" + .FIRST_WORD_FALL_THROUGH("TRUE") // Sets the FIfor FWFT to "TRUE" or "FALSE" + ) + in_buffer + (.ALMOSTEMPTY (), + .ALMOSTFULL (in_buffer_almost_full), + .DO (ingress_buffer_data[7:0]), + .EMPTY (in_buffer_empty), + .FULL (), + .RDCOUNT (), + .RDERR (), + .WRCOUNT (), + .WRERR (), + .DI (ingress_out_data[7:0]), + .RDCLK (clk_io), + .RDEN (ingress_buffer_ready), + .RST (com_rst), + .WRCLK (clk_io), + .WREN (ingress_out_valid) + ); + // Clock domain crossing uart -> logic FIFO_DUALCLOCK_MACRO @@ -147,7 +223,7 @@ module glip_uart_toplevel ) in_fifo (.ALMOSTEMPTY (), - .ALMOSTFULL (in_almost_full), + .ALMOSTFULL (), .DO (fifo_in_data[7:0]), .EMPTY (in_fifo_empty), .FULL (in_fifo_full), @@ -155,12 +231,12 @@ module glip_uart_toplevel .RDERR (), .WRCOUNT (), .WRERR (), - .DI (in_data[7:0]), + .DI (ingress_buffer_data[7:0]), .RDCLK (clk_logic), - .RDEN (fifo_in_ready & fifo_in_valid), - .RST (rst), + .RDEN (fifo_in_ready), + .RST (com_rst), .WRCLK (clk_io), - .WREN (in_valid & in_ready) + .WREN (ingress_buffer_valid) ); // Clock domain crossing logic -> uart @@ -175,7 +251,7 @@ module glip_uart_toplevel out_fifo (.ALMOSTEMPTY (), .ALMOSTFULL (), - .DO (out_data), + .DO (egress_in_data), .EMPTY (out_fifo_empty), .FULL (out_fifo_full), .RDCOUNT (), @@ -184,10 +260,10 @@ module glip_uart_toplevel .WRERR (), .DI (fifo_out_data[7:0]), .RDCLK (clk_io), - .RDEN (out_ready & out_valid), - .RST (rst), + .RDEN (egress_in_ready), + .RST (com_rst), .WRCLK (clk_logic), - .WREN (fifo_out_valid & fifo_out_ready) + .WREN (fifo_out_valid) ); endmodule // glip_uart_toplevel diff --git a/src/backend_uart/sw/backend_uart.c b/src/backend_uart/sw/backend_uart.c index 811bef7..0d14fa7 100644 --- a/src/backend_uart/sw/backend_uart.c +++ b/src/backend_uart/sw/backend_uart.c @@ -30,6 +30,8 @@ #include "glip-protected.h" #include "backend_uart.h" +#include +#include #include #include @@ -39,18 +41,158 @@ #include #include #include +#include #include +#include + +// Forward declarations of local helpers +/** + * Lookup speed (map to termios defines) + * + * Looks up a speed as integer and maps it to termios defines. + * + * @param speed Speed as integer + * @return Baud rate as termios value + */ static int speed_lookup(int speed); /** - * GLIP backend context for the JTAG backend + * Check if time is reached + * + * @param start Start time + * @param ms Milliseconds to check + * @return -ETIMEDOUT if time reached, 0 otherwise + */ +static int check_timeout(struct timeval *start, unsigned long ms); + +/** + * Blocking read from terminal + * + * @param fd File to read from + * @param buffer Buffer to read to + * @param size Size to read + * @param size_read Actual read (only if timeout or error) + * @param timeout Timeout to use, 0 for none + * @return 0 if success, -ETIMEDOUT on timeout, errno else + */ +static int read_blocking(int fd, uint8_t *buffer, size_t size, + size_t *size_read, unsigned int timeout); + +/** + * Blocking write to terminal + * + * @param fd File to write to + * @param buffer Buffer to write from + * @param size Size to write + * @param size_write Actual written (only if timeout or error) + * @param timeout Timeout to use, 0 for none + * @return 0 if success, -ETIMEDOUT on timeout, errno else + */ +static int write_blocking(int fd, uint8_t *buffer, size_t size, + size_t *size_written, unsigned int timeout); + +/** + * Reset the user logic + * + * Sets the user logic reset register + * + * @param fd Terminal to send the reset to + * @param state Value (binary) to set the register to + * @return Always returns 0 + */ +static int reset_logic(int fd, uint8_t state); + +/** + * Reset the communication logic + * + * Sets the communication logic register + * + * @param fd Terminal to use + * @param state Value (binary) to set the register to + * @return Always returns 0 + */ +static int reset_com(int fd, uint8_t state); + +/** + * Update a received debt value + * + * Debt is the credit the logic gives us. Extracts this new tranche + * from the words on the line. + * + * @param ctx Context + * @param first First received word of credit message + * @param second Second received word of credit message + */ +static void update_debt(struct glip_backend_ctx* ctx, uint8_t first, + uint8_t second); + +/** + * Give credit to logic + * + * Increase credit of logic by tranche + * @param ctx Context + * @param tranche Value to add to credit + */ +static int send_credit(struct glip_backend_ctx* ctx, uint16_t tranche); + +/** + * Reset the backend + * + * Resets the buffers, counters etc. + * + * @param ctx Context + * @return 0 on success, -1 otherwise + */ +static int reset(struct glip_backend_ctx* ctx); + +/** + * Communication (POSIX) thread function + * + * @param arg The glip_backend_ctx + * @return Ignorned + */ +void* thread_func(void *arg); + +/** + * Parses an incoming buffer and filters credits + * + * Filter and process credits, write data otherwise + * + * @param ctx Context + * @param buffer Buffer to parse + * @param size Size of the buffer + */ +void parse_buffer(struct glip_backend_ctx *ctx, uint8_t *buffer, + size_t size); + +/*! Maximum tranche we can give in a message */ +static const uint16_t UART_MAX_TRANCHE = 0x3fff; + +/*! Temporary buffer size */ +static const uint16_t TMP_BUFFER_SIZE = 256; + +/** + * GLIP backend context for the UART backend */ struct glip_backend_ctx { - char *device; - int fd; - uint32_t speed; + char *device; /*! Device name */ + int fd; /*! Terminal file */ + uint32_t speed; /*! Baud rate */ + + pthread_t thread; /*! Thread instance */ + + size_t buffer_size; /*! Size of circular buffers */ + + struct cbuf *input_buffer; /*! Input buffer */ + struct cbuf *output_buffer; /*! Output buffer */ + + size_t debt; /*! Current debt (what we can send) */ + size_t credit; /*! Current credit (what logic can send) */ + + volatile int reset_request; /*! Request a logic reset */ + volatile int term_request; /*! Request a termination */ }; /** @@ -65,11 +207,13 @@ struct glip_backend_ctx { */ int gb_uart_new(struct glip_ctx *ctx) { + // Allocate (zero-initialized) memory for our context struct glip_backend_ctx *c = calloc(1, sizeof(struct glip_backend_ctx)); if (!c) { return -1; } + // Register functions for this backend ctx->backend_functions.open = gb_uart_open; ctx->backend_functions.close = gb_uart_close; ctx->backend_functions.logic_reset = gb_uart_logic_reset; @@ -82,6 +226,16 @@ int gb_uart_new(struct glip_ctx *ctx) ctx->backend_ctx = c; + // Set the local buffer sizes and initialize + c->buffer_size = 32768; + if (cbuf_init(&c->input_buffer, c->buffer_size) != 0) { + return -1; + } + + if (cbuf_init(&c->output_buffer, c->buffer_size) != 0) { + return -1; + } + return 0; } @@ -100,11 +254,16 @@ int gb_uart_open(struct glip_ctx *ctx, unsigned int num_channels) { struct glip_backend_ctx *bctx = ctx->backend_ctx; + // Initialize the session variables + bctx->term_request = 0; + bctx->reset_request = 0; + if (num_channels != 1) { err(ctx, "Channel number must be 1!\n"); return -1; } + // Extract parameters if (glip_option_get_char(ctx, "device", (const char**) &bctx->device) != 0) { bctx->device = "/dev/ttyUSB0"; } @@ -113,6 +272,8 @@ int gb_uart_open(struct glip_ctx *ctx, unsigned int num_channels) bctx->speed = 115200; } + // Open the device, the best source for serial terminal handling is: + // http://www.cmrr.umn.edu/~strupp/serial.html bctx->fd = open (bctx->device, O_RDWR | O_NOCTTY | O_NDELAY); if (bctx->fd < 0) { @@ -120,13 +281,16 @@ int gb_uart_open(struct glip_ctx *ctx, unsigned int num_channels) return -1; } + // Translate the integer speed to the proper define value for termios int baud = speed_lookup(bctx->speed); if (baud == -1) { - err(ctx, "Speed not known: %d. Custom baud rates are not supported currently\n", baud); + err(ctx, "Speed not known: %d." + "Custom baud rates are not supported currently\n", baud); return -1; } + // Get the attributes of the terminal to manipulate them struct termios tty; memset (&tty, 0, sizeof tty); if (tcgetattr (bctx->fd, &tty) != 0) @@ -135,41 +299,146 @@ int gb_uart_open(struct glip_ctx *ctx, unsigned int num_channels) return -1; } + // Set line speed cfsetospeed (&tty, baud); cfsetispeed (&tty, baud); // 8N1 - tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; - tty.c_cflag &= ~(PARENB | PARODD); - tty.c_cflag &= ~CSTOPB; + tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; + tty.c_cflag &= ~(PARENB | PARODD); + tty.c_cflag &= ~CSTOPB; // Hardware flow control - tty.c_cflag |= (CLOCAL | CREAD); - tty.c_cflag |= CRTSCTS; + tty.c_cflag |= (CLOCAL | CREAD); + tty.c_cflag |= CRTSCTS; - tty.c_cc[VMIN] = 0; // read doesn't block - tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout + tty.c_cc[VMIN] = 0; // read doesn't block + tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout // Raw - tty.c_lflag = 0; tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); - tty.c_iflag &= ~IGNBRK; - tty.c_iflag &= ~(IXON | IXOFF | IXANY); - - tty.c_oflag = 0; + // No input handling + tty.c_iflag = 0; + // No output handling + tty.c_oflag = 0; + // Write the changed attributes if (tcsetattr (bctx->fd, TCSANOW, &tty) != 0) { - err(ctx, "Cannot set attributes"); + err(ctx, "Cannot set attributes\n"); return -1; } - // Drain the interface - int rv; - uint8_t buffer[16]; + // @todo + // When the default or user-defined speed does not work, we try + // to autodetect the line speed. We try from a given set of + // speeds and take the first one that detects. Unfortunately, we + // can still get false positives as the match pattern is 0xfe. + // This pattern is not so easy to distinguish as it only has one + // low bit in the byte. + int autodetect = 0; // We entered autodetect + // Options to autodetect + int autodetect_candidates[] = { 4000000, 3000000, 2000000, 1000000, + 500000, 230400, 115200, 57600, 38400, 19200, 9600, 0 }; + // Current autodetect (from autodetect_candidates) + int *autodetect_try = 0; + // Indicate if we successfully connected + int success = 0; + do { - rv = read(bctx->fd, buffer, 16); - } while (rv > 0); + if (autodetect) { + // If we are in autodetecting, adopt speed + err(ctx, "Try speed: %d\n", *autodetect_try); + + memset (&tty, 0, sizeof tty); + if (tcgetattr (bctx->fd, &tty) != 0) + { + err(ctx, "Cannot get device attributes\n"); + return -1; + } + + bctx->speed = *autodetect_try; + + baud = speed_lookup(bctx->speed); + cfsetospeed (&tty, baud); + cfsetispeed (&tty, baud); + + if (tcsetattr (bctx->fd, TCSANOW, &tty) != 0) + { + err(ctx, "Cannot set attributes\n"); + return -1; + } + } + + // The ramp up sequence is + // - Set com_rst to high (flush buffers) + int rv; + rv = reset_com(bctx->fd, 1); + + // - Wait for one millisecond to be sure it is in there + usleep(1000); + + // - Drain all remaining in intermediate buffers + do { + uint8_t buffer[128]; + rv = read(bctx->fd, buffer, 128); + } while ((rv > 0) || ((rv == -1) && (errno == EAGAIN))); + + // - De-assert reset + rv = reset_com(bctx->fd, 0); + + // - Read the debt + // We do this just to check the read and if we have detected + // the correct speed + { + uint8_t debt[2]; + size_t size_read; + + // Do a blocking read with 1s timeout to settle + rv = read_blocking(bctx->fd, debt, 2, &size_read, 1000); + if (rv == -ETIMEDOUT) { + // Timeout means we did not read + success = 0; + } else if ((debt[0] != 0xfe) && (debt[1] != 0xfe)) { + // This is we neither had the credit message as first + // nor as second word. We check the second as we + // observed a leading garbage word on some platforms + // with fresh bitstreams + success = 0; + } else { + // Else it seems we have the right speed + success = 1; + } + } + + if (!success) { + // If the ramp up was not successfull + if (!autodetect) { + // Activate autodetect if we were not in it + err(ctx, "Given speed %d did not work with device. " + "Try autodetect..\n", bctx->speed); + autodetect = 1; + autodetect_try = autodetect_candidates; + continue; + } else { + // Increment autodetect otherwise or abort if we + // checked all + autodetect_try++; + if (*autodetect_try == 0) { + err(ctx, "Could not autodetect baud rate.\n"); + return -1; + } + } + } + } while (!success); + + // Reset the communication logic, state and data buffers + reset(bctx); + + // Start the communication thread. From here on all read and write + // operations go to the circular buffers, and the thread handles + // the device interface. + pthread_create(&bctx->thread, 0, thread_func, bctx); return 0; } @@ -186,8 +455,15 @@ int gb_uart_open(struct glip_ctx *ctx, unsigned int num_channels) */ int gb_uart_close(struct glip_ctx *ctx) { + void* rv; struct glip_backend_ctx *bctx = ctx->backend_ctx; + // Send termination request to thread + bctx->term_request = 1; + + pthread_cancel(bctx->thread); + pthread_join(bctx->thread, &rv); + close(bctx->fd); return 0; @@ -195,14 +471,21 @@ int gb_uart_close(struct glip_ctx *ctx) /** * Reset the logic on the target - * - * Not supported as no out-of-band availability - * + * * * @see glip_logic_reset() - * @todo Implement in-band? */ int gb_uart_logic_reset(struct glip_ctx *ctx) { + struct glip_backend_ctx *bctx = ctx->backend_ctx; + + assert(bctx->reset_request == 0); + + // Trigger request in thread + bctx->reset_request = 1; + + // Wait until completed + while (bctx->reset_request == 1) {} + return 0; } @@ -223,26 +506,26 @@ int gb_uart_logic_reset(struct glip_ctx *ctx) int gb_uart_read(struct glip_ctx *ctx, uint32_t channel, size_t size, uint8_t *data, size_t *size_read) { - int rv; + assert(channel == 0); - if (channel != 0) { - err(ctx, "Only channel 0 supported"); - return -1; - } + struct glip_backend_ctx* bctx = ctx->backend_ctx; - struct glip_backend_ctx* bctx = ctx->backend_ctx; + // Check the fill level + size_t fill_level = cbuf_fill_level(bctx->input_buffer); + // We read as much as possible up to size + size_t size_read_req = min(fill_level, size); - do { - rv = read(bctx->fd, data, size); - } while ((rv == -1) && (errno == EAGAIN)); - - if (rv >= 0) { - *size_read = rv; - return 0; - } else { - *size_read = 0; - return errno; - } + // Read from buffer + int rv = cbuf_read(bctx->input_buffer, data, size_read_req); + if (rv < 0) { + err(ctx, "Unable to get data from read buffer, rv = %d\n", rv); + return -1; + } + + // Update actual read information + *size_read = size_read_req; + + return 0; } /** @@ -264,47 +547,55 @@ int gb_uart_read(struct glip_ctx *ctx, uint32_t channel, size_t size, int gb_uart_read_b(struct glip_ctx *ctx, uint32_t channel, size_t size, uint8_t *data, size_t *size_read, unsigned int timeout) { - //return gb_uart_read(ctx, channel, size, data, size_read); - struct timeval tval_start, tval_current, tval_diff; - int rv; - - struct glip_backend_ctx* bctx = ctx->backend_ctx; - - *size_read = 0; - - if (timeout > 0) { - gettimeofday(&tval_start, NULL); - } - - do { - rv = read(bctx->fd, &data[*size_read], size - *size_read); - - if (rv >= 0) { - *size_read += rv; - } - - if ((rv == -1) && (errno != EAGAIN)) { - return errno; - } - - if (timeout > 0) { - gettimeofday(&tval_current, NULL); - tval_diff.tv_sec = tval_current.tv_sec - tval_start.tv_sec; - tval_diff.tv_usec = tval_current.tv_usec - tval_start.tv_usec; - if (tval_current.tv_usec < tval_start.tv_usec) { - tval_diff.tv_sec -= 1; - } - if (tval_current.tv_usec < 0) { - tval_current.tv_usec += 1000000; - } - - if (tval_diff.tv_usec > (timeout * 1000)) { - return -ETIMEDOUT; - } - } - } while(*size_read != size); - - return 0; + int rv; + struct glip_backend_ctx *bctx = ctx->backend_ctx; + struct timespec ts; + + if (size > bctx->buffer_size) { + /* + * This is not a problem for non-blocking reads, but blocking reads will + * block forever in this case as the maximum amount of data ever + * available is limited by the buffer size. + * @todo: This can be solved by loop-reading until timeout + */ + err(ctx, "The read size cannot be larger than %u bytes.", bctx->buffer_size); + return -1; + } + + if (timeout != 0) { + clock_gettime(CLOCK_REALTIME, &ts); + timespec_add_ns(&ts, timeout * 1000 * 1000); + } + + /* + * Wait until sufficient data is available to be read. + */ + if (timeout != 0) { + clock_gettime(CLOCK_REALTIME, &ts); + timespec_add_ns(&ts, timeout * 1000 * 1000); + } + while (cbuf_fill_level(bctx->input_buffer) < size) { + if (timeout == 0) { + rv = cbuf_wait_for_level_change(bctx->input_buffer); + } else { + rv = cbuf_timedwait_for_level_change(bctx->input_buffer, &ts); + } + + if (rv != 0) { + break; + } + } + + /* + * We read whatever data is available, and assume a timeout if the available + * amount of data does not match the requested amount. + */ + *size_read = 0; + rv = gb_uart_read(ctx, channel, size, data, size_read); + if (rv == 0 && size != *size_read) { + return -ETIMEDOUT; + } + return rv; } /** @@ -324,26 +615,15 @@ int gb_uart_read_b(struct glip_ctx *ctx, uint32_t channel, size_t size, int gb_uart_write(struct glip_ctx *ctx, uint32_t channel, size_t size, uint8_t *data, size_t *size_written) { - int rv; + assert(channel == 0); + struct glip_backend_ctx* bctx = ctx->backend_ctx; - if (channel != 0) { - err(ctx, "Only channel 0 supported"); - return -1; - } + unsigned int buf_size_free = cbuf_free_level(bctx->output_buffer); + *size_written = (size > buf_size_free ? buf_size_free : size); - struct glip_backend_ctx* bctx = ctx->backend_ctx; + cbuf_write(bctx->output_buffer, data, *size_written); - do { - rv = write(bctx->fd, data, size); - } while((rv == -1) && (errno = EAGAIN)); - - if (rv >= 0) { - *size_written = rv; - return 0; - } else { - *size_written = 0; - return errno; - } + return 0; } /** @@ -365,46 +645,42 @@ int gb_uart_write(struct glip_ctx *ctx, uint32_t channel, size_t size, int gb_uart_write_b(struct glip_ctx *ctx, uint32_t channel, size_t size, uint8_t *data, size_t *size_written, unsigned int timeout) { - struct timeval tval_start, tval_current, tval_diff; - int rv; - - struct glip_backend_ctx* bctx = ctx->backend_ctx; - - *size_written = 0; - - if (timeout > 0) { - gettimeofday(&tval_start, NULL); - } - - do { - rv = write(bctx->fd, &data[*size_written], size - *size_written); - - if (rv >= 0) { - *size_written += rv; - } - - if ((rv == -1) && (errno != EAGAIN)) { - return errno; - } - - if (timeout > 0) { - gettimeofday(&tval_current, NULL); - tval_diff.tv_sec = tval_current.tv_sec - tval_start.tv_sec; - tval_diff.tv_usec = tval_current.tv_usec - tval_start.tv_usec; - if (tval_current.tv_usec < tval_start.tv_usec) { - tval_diff.tv_sec -= 1; - } - if (tval_current.tv_usec < 0) { - tval_current.tv_usec += 1000000; - } - - if (tval_diff.tv_usec > (timeout * 1000)) { - return -ETIMEDOUT; - } - } - } while(*size_written != size); - - return 0; + assert(channel == 0); + + struct glip_backend_ctx* bctx = ctx->backend_ctx; + struct timespec ts; + + if (timeout != 0) { + clock_gettime(CLOCK_REALTIME, &ts); + timespec_add_ns(&ts, timeout * 1000 * 1000); + } + + size_t size_done = 0; + while (1) { + size_t size_done_tmp = 0; + gb_uart_write(ctx, channel, size - size_done, &data[size_done], + &size_done_tmp); + size_done += size_done_tmp; + + if (size_done == size) { + break; + } + + if (cbuf_free_level(bctx->output_buffer) == 0) { + if (timeout == 0) { + cbuf_wait_for_level_change(bctx->output_buffer); + } else { + cbuf_timedwait_for_level_change(bctx->output_buffer, &ts); + } + } + } + + *size_written = size_done; + if (size != *size_written) { + return -ETIMEDOUT; + } + + return 0; } /** @@ -433,6 +709,7 @@ unsigned int gb_uart_get_fifo_width(struct glip_ctx *ctx) return 1; } +// Translate an integer to the macro #define KNOWN(x) case x: return B##x static int speed_lookup(int speed) { @@ -470,3 +747,348 @@ static int speed_lookup(int speed) { default: return -1; } } + +static int reset_logic(int fd, uint8_t state) { + uint8_t reset[2]; + size_t written; + + reset[0] = 0xfe; + reset[1] = ((state & 0x1) << 1) | 0x81; + + return write_blocking(fd, reset, 2, &written, 0); +} + +static int reset_com(int fd, uint8_t state) { + uint8_t reset[2]; + size_t written; + + reset[0] = 0xfe; + reset[1] = ((state & 0x1) << 1) | 0x85; + + return write_blocking(fd, reset, 2, &written, 0); +} + +int total = 0; + +void parse_buffer(struct glip_backend_ctx *ctx, uint8_t *buffer, size_t size) { + size_t actual, i; + int rv; + + for (i = 0; i < size; i++) { + if (buffer[i] == 0xfe) { + // Check if next item is already in buffer + if ((i + 1) < size) { + if (buffer[i+1] == 0xfe) { + // If this is the data word, write it + assert(ctx->credit > 0); + rv = cbuf_write(ctx->input_buffer, &buffer[i], 1); + assert(rv == 0); + ctx->credit--; + } else { + // This is the first of the credit message + // Check if the next is also in buffer + if ((i + 2) < size) { + update_debt(ctx, buffer[i+1], buffer[i+2]); + // Increment the counter for the second extra word + i++; + } else { + // We have reached the end of the buffer, it is + // safe to use the begin of buffer for the credit + rv = read_blocking(ctx->fd, buffer, 1, &actual, 0); + assert(rv == 0); + update_debt(ctx, buffer[i+1], buffer[0]); + } + } + // Increment the counter for the first extra word + i++; + } else { + // If the next item was not in buffer, read another one, + // we can now reuse the buffer + rv = read_blocking(ctx->fd, buffer, 1, &actual, 0); + assert(rv == 0); + + if (buffer[0] == 0xfe) { + // That was the data word, write it + assert(ctx->credit > 0); + rv = cbuf_write(ctx->input_buffer, &buffer[0], 1); + assert(rv == 0); + ctx->credit--; + } else { + // We received a credit, read the next + rv = read_blocking(ctx->fd, &buffer[1], 1, &actual, 0); + assert(rv == 0); + update_debt(ctx, buffer[0], buffer[1]); + } + } + } else { + // This is a data word + assert(ctx->credit > 0); + rv = cbuf_write(ctx->input_buffer, &buffer[i], 1); + assert(rv == 0); + ctx->credit--; + } + } +} + +int wtotal = 0; + +void* thread_func(void *arg) { + uint8_t buffer[TMP_BUFFER_SIZE]; + size_t avail, actual; + int rv; + + struct glip_backend_ctx *ctx = (struct glip_backend_ctx*) arg; + + while (1) { + // Check for reset + if (ctx->reset_request == 1) { + rv = reset_logic(ctx->fd, 1); + assert(rv == 0); + + rv = reset_logic(ctx->fd, 0); + assert(rv == 0); + ctx->reset_request = 0; + } + + if (ctx->term_request) { + // Termination requested from user + break; + } + + // Read + avail = cbuf_free_level(ctx->input_buffer); + if (avail == 0) { + // We are only expecting a credit message now + // Check if there is one + rv = read(ctx->fd, buffer, 1); + if (rv == 1) { + assert(buffer[0] == 0xfe); + rv = read_blocking(ctx->fd, buffer, 2, &actual, 0); + assert(rv == 0); + assert(actual == 2); + update_debt(ctx, buffer[0], buffer[1]); + } + } else { + // Read a chunk of data if the circular buffer can accept it + size_t size = min(avail, TMP_BUFFER_SIZE); + rv = read(ctx->fd, buffer, size); + if (rv > 0) { + parse_buffer(ctx, buffer, rv); + } + } + + // Write + if (ctx->debt > 0) { + size_t size = min(ctx->debt, TMP_BUFFER_SIZE); + avail = cbuf_fill_level(ctx->output_buffer); + if (avail > 0) { + // If there is data to be transfered, take the smallest + // number of available words, the debt size and the + // size of the temporaty buffer as transfer size + size = min(avail, size); + + // Read the words + assert(cbuf_read(ctx->output_buffer, buffer, size) != -EINVAL); + + for (size_t i = 0; i < size; i++) { + // Write each word + rv = write_blocking(ctx->fd, &buffer[i], 1, &actual, 0); + assert(rv == 0); + assert(actual == 1); + if (buffer[i] == 0xfe) { + // .. and repeat the marker word 0xfe + rv = write_blocking(ctx->fd, &buffer[i], 1, &actual, 0); + assert(rv == 0); + assert(actual == 1); + } + } + ctx->debt -= size; + } + } + + // Update credit if necessary + if (ctx->credit < ctx->buffer_size-UART_MAX_TRANCHE) { + // Give new credit + ctx->credit += UART_MAX_TRANCHE; + + send_credit(ctx, UART_MAX_TRANCHE); + } + } + + return 0; +} + +static int read_blocking(int fd, uint8_t *buffer, size_t size, + size_t *size_read, unsigned int timeout) { + struct timeval tval_start; + int rv; + + *size_read = 0; + + if (timeout > 0) { + gettimeofday(&tval_start, NULL); + } + + do { + // Try to read as many as we still have left to read + rv = read(fd, &buffer[*size_read], size - *size_read); + + if (rv >= 0) { + // Update number of read + *size_read += rv; + } + + if ((rv == -1) && (errno != EAGAIN)) { + // We tolerate EAGAIN errors, but cannot tolerate others + return errno; + } + + if (timeout > 0) { + // Check if the timeout has occured + if (check_timeout(&tval_start, timeout)) { + return -ETIMEDOUT; + } + } + } while(*size_read != size); + + return 0; +} + +static int write_blocking(int fd, uint8_t *buffer, size_t size, + size_t *size_written, unsigned int timeout) { + struct timeval tval_start; + int rv; + + *size_written = 0; + + if (timeout > 0) { + gettimeofday(&tval_start, NULL); + } + + do { + // Write as many as possible from remaining + rv = write(fd, &buffer[*size_written], size - *size_written); + + if (rv >= 0) { + // Update number of actually written + *size_written += rv; + } + + if ((rv == -1) && (errno != EAGAIN)) { + // Cannot tolerate errors other then EAGAIN + return errno; + } + + if (timeout > 0) { + // Check for a timeout + if (check_timeout(&tval_start, timeout)) { + return -ETIMEDOUT; + } + } + } while(*size_written != size); + + return 0; +} + +static int check_timeout(struct timeval *start, unsigned long ms) { + struct timeval tval_current, tval_diff; + gettimeofday(&tval_current, NULL); + + // Calculate the difference + tval_diff.tv_sec = tval_current.tv_sec - start->tv_sec; + tval_diff.tv_usec = tval_current.tv_usec - start->tv_usec; + + // Microsecond overflows increase the second + if (tval_current.tv_usec < start->tv_usec) { + tval_diff.tv_sec += 1; + tval_diff.tv_usec += 1000000; + } + + // Check if we reached the timeout + if ((tval_diff.tv_sec * 1000000 + tval_diff.tv_usec) + > ((signed) ms * 1000)) { + return -ETIMEDOUT; + } + + return 0; +} + +static void update_debt(struct glip_backend_ctx* ctx, uint8_t first, + uint8_t second) { + // Assemble new debt tranche from the two control datagrams + ctx->debt += (((first >> 1) & 0x7f) << 8) | second; +} + +static int send_credit(struct glip_backend_ctx* ctx, uint16_t tranche) { + uint8_t credit[3]; + size_t written; + + // Assemble the message datagrams + credit[0] = 0xfe; + credit[1] = 0x1 | ((tranche >> 8) << 1); + credit[2] = tranche & 0xff; + + if (write_blocking(ctx->fd, credit, 3, &written, 0) != 0) { + return -1; + } + + return 0; +} + +static int reset(struct glip_backend_ctx* ctx) { + int rv; + + // Assert reset + rv = reset_com(ctx->fd, 1); + if (rv != 0) { + return -1; + } + + // Drain the interface + uint8_t buffer[16]; + do { + rv = read(ctx->fd, buffer, 16); + } while (rv > 0); + + // De-assert reset + rv = reset_com(ctx->fd, 0); + if (rv != 0) { + return -1; + } + + // Clear the buffers + rv = cbuf_discard(ctx->input_buffer, cbuf_fill_level(ctx->input_buffer)); + assert(rv == 0); + + rv = cbuf_discard(ctx->output_buffer, cbuf_fill_level(ctx->output_buffer)); + assert(rv == 0); + + // Reset values to defaults + ctx->credit = UART_MAX_TRANCHE; + ctx->debt = 0; + + // Send our initial credit tranche to the logic + send_credit(ctx, UART_MAX_TRANCHE); + + // Read the debt from the logic + uint8_t debt[3]; + size_t read; + rv = read_blocking(ctx->fd, debt, 3, &read, 0); + assert(rv == 0); + + // Strangely, the UART sometimes returns a trash byte on the first + // access after the bitstream was loaded. Ignore this. + if (debt[0] != 0xfe) { + debt[0] = debt[1]; + debt[1] = debt[2]; + + rv = read_blocking(ctx->fd, &debt[2], 1, &read, 0); + assert(rv == 0); + } + + assert(debt[0] == 0xfe); + + update_debt(ctx, debt[1], debt[2]); + + return 0; +} diff --git a/src/common/logic/credit/verilog/README.md b/src/common/logic/credit/verilog/README.md new file mode 100644 index 0000000..ed0f9eb --- /dev/null +++ b/src/common/logic/credit/verilog/README.md @@ -0,0 +1,2 @@ +# Helpers for Credit-based Flow Control + diff --git a/src/common/logic/credit/verilog/creditor.v b/src/common/logic/credit/verilog/creditor.v new file mode 100644 index 0000000..b1bf469 --- /dev/null +++ b/src/common/logic/credit/verilog/creditor.v @@ -0,0 +1,89 @@ +/* Copyright (c) 2016 by the author(s) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * ============================================================================= + * + * Unit that gives credits to the upstream communication partner + * + * Author(s): + * Stefan Wallentowitz + */ + +module creditor + #(parameter WIDTH = 1'bx, + parameter CREDIT_WIDTH = 1'bx, + parameter INITIAL_VALUE = {WIDTH{1'bx}}) + ( + input clk, + input rst, + + input payback, + output reg [CREDIT_WIDTH-1:0] credit, + input borrow, + output reg grant, + + output error + ); + + reg [WIDTH-1:0] resources = INITIAL_VALUE; + reg [WIDTH:0] nxt_resources; + reg [CREDIT_WIDTH-1:0] nxt_credit; + + // Error is an overflow of the resources. The host cannot payback + // more than originally granted. + assign error = nxt_resources[WIDTH] | (resources > INITIAL_VALUE); + + // Sample registers for remaining resources and credit + always @(posedge clk) begin + if (rst) begin + resources <= INITIAL_VALUE; + credit <= 0; + end else begin + resources <= nxt_resources[WIDTH-1:0]; + credit <= nxt_credit; + end + end + + always @(*) begin + nxt_resources = resources; + nxt_credit = credit; + + grant = 0; + + if (payback) begin + nxt_resources = resources + 1; + end else if (borrow) begin + grant = 1; + if (WIDTH > CREDIT_WIDTH) begin + if (|resources[WIDTH-1:CREDIT_WIDTH]) begin + nxt_credit = {CREDIT_WIDTH{1'b1}}; + nxt_resources = resources - nxt_credit; + end else begin + nxt_credit = resources; + nxt_resources = 0; + end + end else begin + nxt_credit = { {CREDIT_WIDTH-WIDTH{1'b0}}, resources}; + nxt_resources = 0; + end // else: !if(WIDTH > CREDIT_WIDTH) + end + end + +endmodule // glip_creditgen diff --git a/src/common/logic/credit/verilog/debtor.v b/src/common/logic/credit/verilog/debtor.v new file mode 100644 index 0000000..0d51efc --- /dev/null +++ b/src/common/logic/credit/verilog/debtor.v @@ -0,0 +1,72 @@ +/* Copyright (c) 2016 by the author(s) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * ============================================================================= + * + * Unit that gets credits from the upstream communication partner + * + * Author(s): + * Stefan Wallentowitz + */ + +module debtor + #(parameter WIDTH = 1'bx, + parameter TRANCHE_WIDTH = 1'bx) + ( + input clk, + input rst, + + input payback, + output owing, + input [TRANCHE_WIDTH-1:0] tranche, + input lend, + + output reg error + ); + + reg [WIDTH-1:0] credit; + reg [WIDTH:0] nxt_credit; + + assign owing = |credit; + + always @(posedge clk) begin + if (rst) begin + credit <= 0; + end else begin + credit <= nxt_credit[WIDTH-1:0]; + end + end + + always @(*) begin + nxt_credit = credit; + error = 0; + + if (lend & !payback) begin + nxt_credit = credit + tranche; + error = nxt_credit[WIDTH]; + end else if (payback & !lend) begin + nxt_credit = nxt_credit - 1; + error = ~|credit; + end else if (lend & payback) begin + nxt_credit = nxt_credit + tranche - 1; + end + end // always @ (*) + +endmodule // glip_debtor