hw/ip/usb_fs_nb_pe/rtl/usb_fs_nb_in_pe.sv Cov: 100%

   1: // Copyright lowRISC contributors.
   2: // Copyright Luke Valenty (TinyFPGA project)
   3: // Licensed under the Apache License, Version 2.0, see LICENSE for details.
   4: // SPDX-License-Identifier: Apache-2.0
   5: 
   6: // USB Full Speed Non-Buffered Protocol Engine for IN endpoints
   7: //
   8: // Decode IN requests from host and respond with data if there is any
   9: //
  10: // Based on usb_fs_in_pe.v from the TinyFPGA-Bootloader project but
  11: // this version contains no packet buffers
  12: 
  13: module usb_fs_nb_in_pe #(
  14:   parameter logic [4:0] NumInEps = 11,
  15:   parameter int unsigned MaxInPktSizeByte = 32,
  16:   parameter int unsigned PktW = $clog2(MaxInPktSizeByte),
  17:   parameter int unsigned InEpW = $clog2(NumInEps)
  18: ) (
  19:   input  logic               clk_48mhz_i,
  20:   input  logic               rst_ni,
  21:   input  logic               link_reset_i,
  22:   input  logic [6:0]         dev_addr_i,
  23: 
  24: 
  25:   ////////////////////
  26:   // endpoint interface
  27:   ////////////////////
  28:   output logic [3:0]            in_ep_current_o, // Other signals addressed to this ep
  29:   output logic                  in_ep_rollback_o, // Bad termination, rollback transaction
  30:   output logic                  in_ep_acked_o, // good termination, transaction complete
  31:   output logic [PktW - 1:0]     in_ep_get_addr_o, // Offset requested (0..pktlen)
  32:   output logic                  in_ep_data_get_o, // Accept data (get_addr advances too)
  33:   output logic                  in_ep_newpkt_o, // New IN packet starting (with in_ep_current_o update)
  34:   input  logic [NumInEps-1:0]   in_ep_stall_i, // Endpoint in a stall state
  35:   input  logic [NumInEps-1:0]   in_ep_has_data_i, // Endpoint has data to supply
  36:   input  logic [7:0]            in_ep_data_i, // Data for current get_addr
  37:   input  logic [NumInEps-1:0]   in_ep_data_done_i, // Set when out of data
  38:   input  logic [NumInEps-1:0]   in_ep_iso_i, // Configure endpoint in isochronous mode
  39: 
  40:   input  logic [NumInEps-1:0]   data_toggle_clear_i, // Clear the data toggles for an EP
  41: 
  42:   ////////////////////
  43:   // rx path
  44:   ////////////////////
  45: 
  46:   // Strobed on reception of packet.
  47:   input  logic              rx_pkt_start_i,
  48:   input  logic              rx_pkt_end_i,
  49:   input  logic              rx_pkt_valid_i,
  50: 
  51:   // Most recent packet received.
  52:   input  logic [3:0]        rx_pid_i,
  53:   input  logic [6:0]        rx_addr_i,
  54:   input  logic [3:0]        rx_endp_i,
  55: 
  56:   ////////////////////
  57:   // tx path
  58:   ////////////////////
  59: 
  60:   // Strobe to send new packet.
  61:   output logic              tx_pkt_start_o,
  62:   input  logic              tx_pkt_end_i,
  63:   // Packet type to send
  64:   output logic [3:0]        tx_pid_o,
  65: 
  66:   // Data payload to send if any
  67:   output logic              tx_data_avail_o,
  68:   input  logic              tx_data_get_i,
  69:   output logic [7:0]        tx_data_o
  70: );
  71:   // suppress warnings
  72:   logic                      unused_1, unused_2;
  73:   assign unused_1 = tx_pkt_end_i;
  74:   assign unused_2 = rx_pkt_start_i;
  75: 
  76:   ////////////////////////////////////////////////////////////////////////////////
  77:   // in transfer state machine
  78:   ////////////////////////////////////////////////////////////////////////////////
  79: 
  80:   import usb_consts_pkg::*;
  81: 
  82:   typedef enum logic [1:0] {
  83:     StIdle,
  84:     StRcvdIn,
  85:     StSendData,
  86:     StWaitAck
  87:   } state_in_e;
  88: 
  89:   state_in_e  in_xfr_state;
  90:   state_in_e  in_xfr_state_next;
  91: 
  92:   logic in_xfr_end;
  93: 
  94:   assign in_ep_acked_o = in_xfr_end;
  95: 
  96:   // data toggle state
  97:   logic [NumInEps - 1:0] data_toggle_q, data_toggle_d;
  98: 
  99:   // endpoint data buffer
 100:   logic                    token_received, setup_token_received, in_token_received, ack_received;
 101:   logic                    more_data_to_send;
 102: 
 103:   // Make widths work
 104:   logic [InEpW - 1 : 0]    in_ep_index;
 105:   assign in_ep_index = in_ep_current_o[0 +: InEpW];
 106: 
 107:   // More syntax so can compare with enum
 108:   usb_pid_type_e rx_pid_type;
 109:   usb_pid_e      rx_pid;
 110:   assign rx_pid_type = usb_pid_type_e'(rx_pid_i[1:0]);
 111:   assign rx_pid      = usb_pid_e'(rx_pid_i);
 112: 
 113:   assign token_received =
 114:     rx_pkt_end_i &&
 115:     rx_pkt_valid_i &&
 116:     rx_pid_type == UsbPidTypeToken &&
 117:     rx_addr_i == dev_addr_i &&
 118:     {1'b0, rx_endp_i} < NumInEps;
 119: 
 120:   assign setup_token_received =
 121:     token_received &&
 122:     rx_pid == UsbPidSetup;
 123: 
 124:   assign in_token_received =
 125:     token_received &&
 126:     rx_pid == UsbPidIn;
 127: 
 128:   assign ack_received =
 129:     rx_pkt_end_i &&
 130:     rx_pkt_valid_i &&
 131:     rx_pid == UsbPidAck;
 132: 
 133:   assign more_data_to_send = in_ep_has_data_i[in_ep_index] & ~in_ep_data_done_i[in_ep_index];  // lint: in_ep_index range was checked
 134: 
 135:   assign tx_data_avail_o = logic'(in_xfr_state == StSendData) & more_data_to_send;
 136: 
 137:   ////////////////////////////////////////////////////////////////////////////////
 138:   // in transfer state machine
 139:   ////////////////////////////////////////////////////////////////////////////////
 140: 
 141:   logic rollback_in_xfr;
 142: 
 143:   always_comb begin
 144:     in_xfr_state_next = in_xfr_state;
 145:     in_xfr_end = 1'b0;
 146:     tx_pkt_start_o = 1'b0;
 147:     tx_pid_o = 4'b0000;
 148:     rollback_in_xfr = 1'b0;
 149:     unique case (in_xfr_state)
 150:       StIdle: begin
 151:         if (in_token_received) begin
 152:           in_xfr_state_next = StRcvdIn;
 153:         end else begin
 154:           in_xfr_state_next = StIdle;
 155:         end
 156:       end
 157: 
 158:       StRcvdIn: begin
 159:         tx_pkt_start_o = 1'b1; // Need to transmit NACK/STALL or DATA
 160: 
 161:         if (in_ep_stall_i[in_ep_index]) begin  // lint: in_ep_index range was checked
 162:           in_xfr_state_next = StIdle;
 163:           tx_pid_o = {UsbPidStall}; // STALL
 164:         end else if (in_ep_iso_i[in_ep_index]) begin
 165:           // ISO endpoint
 166:           // We always need to transmit. When no data is available, we send
 167:           // a zero-length packet
 168:           in_xfr_state_next = StSendData;
 169:           tx_pid_o = {data_toggle_q[in_ep_index], 1'b0, {UsbPidTypeData}}; // DATA0/1 lint: checked
 170: 
 171:         end else if (in_ep_has_data_i[in_ep_index]) begin  // lint: in_ep_index range was checked
 172:           in_xfr_state_next = StSendData;
 173:           tx_pid_o = {data_toggle_q[in_ep_index], 1'b0, {UsbPidTypeData}}; // DATA0/1 lint: checked
 174:         end else begin
 175:           in_xfr_state_next = StIdle;
 176:           tx_pid_o = {UsbPidNak}; // NAK
 177:         end
 178:       end
 179: 
 180:       StSendData: begin
 181:         // Use &in_ep_get_addr so width can vary, looking for all ones
 182:         if ((!more_data_to_send) || ((&in_ep_get_addr_o) && tx_data_get_i)) begin
 183:           if (in_ep_iso_i[in_ep_index]) begin
 184:             in_xfr_state_next = StIdle; // no ACK for ISO EPs
 185:           end else begin
 186:             in_xfr_state_next = StWaitAck;
 187:           end
 188:         end else begin
 189:           in_xfr_state_next = StSendData;
 190:         end
 191:       end
 192: 
 193:       StWaitAck: begin
 194:         // FIXME: need to handle smash/timeout
 195:         if (ack_received) begin
 196:           in_xfr_state_next = StIdle;
 197:           in_xfr_end = 1'b1;
 198:         end else if (in_token_received) begin
 199:           in_xfr_state_next = StRcvdIn;
 200:           rollback_in_xfr = 1'b1;
 201:         end else if (rx_pkt_end_i) begin
 202:           in_xfr_state_next = StIdle;
 203:           rollback_in_xfr = 1'b1;
 204:         end else begin
 205:           in_xfr_state_next = StWaitAck;
 206:         end
 207:       end
 208:     endcase
 209:   end
 210: 
 211:   always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
 212:     if (!rst_ni) begin
 213:       tx_data_o <= '0;
 214:     end else begin
 215:       tx_data_o <= in_ep_data_i;
 216:     end
 217: 
 218:   end
 219: 
 220:   always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
 221:     if (!rst_ni) begin
 222:       in_xfr_state <= StIdle;
 223:       in_ep_rollback_o <= 1'b0;
 224:     end else if (link_reset_i) begin
 225:       in_xfr_state <= StIdle;
 226:       in_ep_rollback_o <= 1'b0;
 227:     end else begin
 228:       in_xfr_state <= in_xfr_state_next;
 229:       in_ep_rollback_o <= rollback_in_xfr;
 230:     end
 231:   end
 232: 
 233:   always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
 234:     if (!rst_ni) begin
 235:       in_ep_get_addr_o <= '0;
 236:     end else begin
 237:       if (in_xfr_state == StIdle) begin
 238:         in_ep_get_addr_o <= '0;
 239:       end else if ((in_xfr_state == StSendData) && tx_data_get_i) begin
 240:         in_ep_get_addr_o <= in_ep_get_addr_o + 1'b1;
 241:       end
 242:     end
 243:   end
 244: 
 245:   always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
 246:     if (!rst_ni) begin
 247:       in_ep_newpkt_o <= 1'b0;
 248:       in_ep_current_o <= '0;
 249:     end else begin
 250:       if (in_token_received) begin
 251:         in_ep_current_o <= rx_endp_i;
 252:         in_ep_newpkt_o <= 1'b1;
 253:       end else begin
 254:         in_ep_newpkt_o <= 1'b0;
 255:       end
 256:     end
 257:   end
 258: 
 259:   always_comb begin : proc_data_toggle_d
 260:     data_toggle_d = data_toggle_q;
 261: 
 262:     if (setup_token_received) begin
 263:       // Ok because token_recieved only triggers if rx_endp_i is in range
 264:       data_toggle_d[rx_endp_i[0 +: InEpW]] = 1'b1;
 265:     end else if ((in_xfr_state == StWaitAck) && ack_received) begin
 266:       data_toggle_d[in_ep_index] = ~data_toggle_q[in_ep_index]; // lint: range was checked
 267:     end
 268: 
 269:     data_toggle_d = data_toggle_d & ~data_toggle_clear_i;
 270:   end
 271: 
 272:   always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
 273:     if (!rst_ni) begin
 274:       data_toggle_q <= '0; // Clear for all endpoints
 275:     end else if (link_reset_i) begin
 276:       data_toggle_q <= '0; // Clear for all endpoints
 277:     end else begin
 278:       data_toggle_q <= data_toggle_d;
 279:     end
 280:   end
 281: 
 282:   always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
 283:     if (!rst_ni) begin
 284:       in_ep_data_get_o <= 1'b0;
 285:     end else begin
 286:       if ((in_xfr_state == StSendData) && tx_data_get_i) begin
 287:         in_ep_data_get_o <= 1'b1;
 288:       end else begin
 289:         in_ep_data_get_o <= 1'b0;
 290:       end
 291:     end
 292:   end
 293: 
 294: 
 295: endmodule
 296: