aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/top/x400/dboards/ctrlport_byte_serializer.v
blob: 3bb6df3c6c8dd75dcec3bd970248c1837d48ea1e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//
// Copyright 2021 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: ctrlport_byte_serializer
//
// Description:
//   Serializes CtrlPort requests into a byte stream.
//
//   The serialized data is similar to an AXI4-Streaming interface with one byte
//   per clock cycle and a valid signal. Direction controls the current transmission
//   direction. 0 = Master to Slave, 1 = Slave to Master, where this module is the
//   master. Direction is always present in direction master to slave where the
//   other signals valid and data can be shared on a tri-state bus.
//
//   The transmission is defined as described below. The bytes are transmitted MSB
//   first.
//   Write request:
//   1'b1 = write, 15 bit address, 32 bit data (MOSI) = 6 bytes request
//   5 bit padding, 1 bit ack, 2 bit status = 1 byte response
//   Read request:
//   1'b0 = read, 15 bit address = 2 bytes request
//   32 bit data, 5 bit padding, 1 bit ack, 2 bit status = 5 bytes response
//
//   When sharing valid and data signal lines between master and slave the
//   timing is defined as:
//
//   clk   __/--\__/--\__/--\__/--\__/--\__/--\__/--\__/--\__/--\__/--\__/--\__
//   direction            _______________________/-----------------\____________
//   master output enable _____/-----------------\______________________________
//   slave output enable  _____________________________/-----------------\______
//   valid & data         zzzzz| Master driven   | zzz | Slave driven    | zzzzz
//   transaction                <--- Request ---><--- Response --->
//
//   The slave should use the direction signal to derive it's own output enable
//   leaving the master the option to terminate the transaction.
//   On switch from slave to master the direction has to be changed at least
//   one clock cycle before enabling the master output enable to avoid driving the
//   bus from two sources.
//

`default_nettype none

module ctrlport_byte_serializer (
  // Clock and Reset
  input wire ctrlport_clk,
  input wire ctrlport_rst,

  // Request
  input  wire        s_ctrlport_req_wr,
  input  wire        s_ctrlport_req_rd,
  input  wire [19:0] s_ctrlport_req_addr,
  input  wire [31:0] s_ctrlport_req_data,

  // Response
  output reg         s_ctrlport_resp_ack = 1'b0,
  output reg  [ 1:0] s_ctrlport_resp_status = 2'b0,
  output reg  [31:0] s_ctrlport_resp_data = 32'b0,

  // GPIO interface
  input  wire [ 7:0] bytestream_data_in,
  input  wire        bytestream_valid_in,
  output reg  [ 7:0] bytestream_data_out = 8'b0,
  output reg         bytestream_valid_out = 1'b0,
  output reg         bytestream_direction = 1'b0,
  output reg         bytestream_output_enable = 1'b1
);

  `include "../../../lib/rfnoc/core/ctrlport.vh"

  //---------------------------------------------------------------
  // transfer constants
  //---------------------------------------------------------------
  // derived from the transaction format (see description above)
  localparam NUM_BYTES_TX_READ = 2;
  localparam NUM_BYTES_RX_READ = 5;
  localparam NUM_BYTES_TX_WRITE = 6;
  localparam NUM_BYTES_RX_WRITE = 1;

  localparam TIMEOUT_COUNTER_WIDTH = 6;

  //----------------------------------------------------------
  // FSM to handle transfers
  //----------------------------------------------------------
  localparam IDLE           = 3'd0;
  localparam SENDING        = 3'd1;
  localparam INIT_RX        = 3'd2;
  localparam DIR_SWITCH     = 3'd3;
  localparam DIR_SWITCH_DLY = 3'd4;
  localparam RECEIVING      = 3'd5;
  localparam ACK            = 3'd6;
  localparam TIMEOUT        = 3'd7;

  // input registers to relax input timing
  reg [7:0] bytestream_data_in_reg = 8'b0;
  reg       bytestream_valid_in_reg = 1'b0;
  always @ (posedge ctrlport_clk) begin
    bytestream_data_in_reg <= bytestream_data_in;
    bytestream_valid_in_reg <= bytestream_valid_in;
  end

  // internal registers
  reg                      [ 2:0] state           = IDLE;
  reg  [NUM_BYTES_TX_WRITE*8-1:0] request_cache   = {NUM_BYTES_TX_WRITE*8{1'b0}};
  reg  [ NUM_BYTES_RX_READ*8-1:0] response_cache  = {NUM_BYTES_RX_READ*8{1'b0}};
  reg                      [ 2:0] byte_counter    = 3'b0;
  reg                             write_transfer  = 1'b0;
  reg [TIMEOUT_COUNTER_WIDTH-1:0] timeout_counter = {TIMEOUT_COUNTER_WIDTH {1'b0}};

  always @ (posedge ctrlport_clk) begin
    if (ctrlport_rst) begin
      state <= IDLE;

      bytestream_valid_out     <= 1'b0;
      byte_counter             <= 3'b0;
      bytestream_direction     <= 1'b0;
      bytestream_output_enable <= 1'b1;

      s_ctrlport_resp_ack      <= 1'b0;
    end else begin
      case (state)
        IDLE: begin
          // reset values from previous states
          s_ctrlport_resp_ack      <= 1'b0;
          bytestream_valid_out     <= 1'b0;
          bytestream_output_enable <= 1'b1;
          byte_counter             <= 3'b0;
          timeout_counter          <= {TIMEOUT_COUNTER_WIDTH {1'b0}};

          // start transmission on read or write
          if (s_ctrlport_req_rd || s_ctrlport_req_wr) begin
            state <= SENDING;
            request_cache  <= {s_ctrlport_req_wr, s_ctrlport_req_addr[14:0], s_ctrlport_req_data};
            write_transfer <= s_ctrlport_req_wr;
          end
        end

        // send as many bytes as required for read / write
        SENDING: begin
          bytestream_data_out  <= request_cache[NUM_BYTES_TX_WRITE*8-8+:8];
          request_cache        <= {request_cache[NUM_BYTES_TX_WRITE*8-9:0], 8'b0};
          bytestream_valid_out <= 1'b1;
          byte_counter         <= byte_counter + 1'b1;

          if ((write_transfer && byte_counter == NUM_BYTES_TX_WRITE-1) ||
               (~write_transfer && byte_counter == NUM_BYTES_TX_READ-1)) begin
            state <= INIT_RX;
          end
        end

        // first cycle for switching to make sure valid signal is driven
        // from slave when being in RECEIVING state
        INIT_RX: begin
          state <= DIR_SWITCH;

          bytestream_direction     <= 1'b1;
          bytestream_output_enable <= 1'b0;
          bytestream_valid_out     <= 1'b0;

          byte_counter <= 3'b0;
        end

        // second switching cycle to let CPLD load the lines based on direction
        DIR_SWITCH: begin
          state <= DIR_SWITCH_DLY;
        end

        // third switching cycle to compensate data input register
        DIR_SWITCH_DLY: begin
          state <= RECEIVING;
        end

        // wait for response to be received
        // immediately change direction after successful reception to have one
        // clock cycle of pause to avoid double driving the bus
        RECEIVING: begin
          timeout_counter <= timeout_counter + 1;

          if (bytestream_valid_in_reg) begin
            byte_counter <= byte_counter + 1'b1;
            response_cache = {response_cache[NUM_BYTES_RX_READ*8-9:0], bytestream_data_in_reg};

            if ((write_transfer && byte_counter == NUM_BYTES_RX_WRITE-1) ||
               (~write_transfer && byte_counter == NUM_BYTES_RX_READ-1)) begin
              state                <= ACK;
              bytestream_direction <= 1'b0;
            end
          end

          if (timeout_counter == {TIMEOUT_COUNTER_WIDTH {1'b1}}) begin
            state                <= TIMEOUT;
            bytestream_direction <= 1'b0;
          end
        end

        // issue ctrlport response
        ACK: begin
          state <= IDLE;

          s_ctrlport_resp_ack    <= 1'b1;
          // status based on received ack
          s_ctrlport_resp_status <= response_cache[2] ? response_cache[1:0] : CTRL_STS_CMDERR;
          if (write_transfer) begin
            s_ctrlport_resp_data <= 32'b0;
          end else begin
            s_ctrlport_resp_data <= response_cache[39:8];
          end
        end

        TIMEOUT: begin
          state <= IDLE;

          s_ctrlport_resp_ack    <= 1'b1;
          s_ctrlport_resp_status <= CTRL_STS_CMDERR;
          s_ctrlport_resp_data   <= 32'b0;
        end

        default: begin
          state <= IDLE;
        end
      endcase
    end
  end

endmodule

`default_nettype wire