diff --git a/hw/newusb/new_usb_devicewritebackhandler.sv b/hw/newusb/new_usb_devicewritebackhandler.sv new file mode 100644 index 00000000..56ffc9fa --- /dev/null +++ b/hw/newusb/new_usb_devicewritebackhandler.sv @@ -0,0 +1,8 @@ +// Copyright 2024 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 +// +// Fabian Hauser +// +/// Handles the data received from the devices. + diff --git a/hw/newusb/new_usb_dmaoutputqueueED.sv b/hw/newusb/new_usb_dmaoutputqueueED.sv new file mode 100644 index 00000000..df4a5222 --- /dev/null +++ b/hw/newusb/new_usb_dmaoutputqueueED.sv @@ -0,0 +1,149 @@ +// Copyright 2024 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 +// +// Fabian Hauser +// +/// Two-stage output queue of the DMA for endpoint descriptors +/// Additional stash register for nonperiodic/periodic context switches +/// Checks validity at secondin because at head we do +/// double length reads from the DMA like in an array which is not HCD spec but predicted. + +// Todo: validity check secondin +// Todo: register hccurrent flow check +// Todo: status bits functionality +// Todo: stashing for bulk control? + +package new_usb_dmaoutputqueueED_pkg; + + typedef struct packed { + struct packed { + logic [10:0] MPS; // 26:16 + logic F; // 15 + logic K; // 14 + logic S; // 13 + logic [12:11] D; // 12:11 + logic [10:7] EN; // 10:7 + logic [6:0] FA; // 6:0 + } status; + struct packed { + logic [27:0] address; // 31:4 + logic C; // 1 + logic H; // 0 + } headTD; + struct packed { + logic [27:0] address; // 31:4 + } nextED; + } endpoint_descriptor; + +endpackage + +module new_usb_dmaoutputqueueED import new_usb_dmaoutputqueueED_pkg::*; ( + /// control + input logic clk_i, + input logic rst_ni, + input logic pop_i, // @pop store currenthead and do stash or secondin -> firstin + input logic pop_ready_o, + input logic context_switch_np2p_i, // nonperiodic to periodic + input logic context_switch_p2np_i, // periodic to nonperiodic + output logic empty_secondin_o, // request new ED + output logic firstin_valid_o, + output logic secondin_valid_o, + output logic secondin_loaded_o, + /// data input + input logic [31:0] dma_data_i, + input logic dma_valid_i, + output logic dma_ready_o, + /// external ED access + output endpoint_descriptor secondin, + output endpoint_descriptor firstin +); + endpoint_descriptor stash; + endpoint_descriptor secondinmux; + + logic [31:0] dword0; + logic [31:0] dword1; + logic [31:0] dword2; + logic [31:0] dword3; + + assign dword0 [26:16] = secondin.status.MPS; + assign dword0 [15] = secondin.status.F; + assign dword0 [14] = secondin.status.K; + assign dword0 [13] = secondin.status.S; + assign dword0 [12:11] = secondin.status.D; + assign dword0 [10:7] = secondin.status.EN; + assign dword0 [6:0] = secondin.status.FA; + assign dword2 [31:4] = secondin.headTD.address; + assign dword2 [1] = secondin.headTD.C; + assign dword2 [0] = secondin.headTD.H; + assign dword3 [31:4] = secondin.nextED.address; + + // control logic + assign dword1 [31:4] = tailp; // tailpointerTD only used for empty check + assign noTD = (tailp == secondin.headTD.address); // we have no TDs or it's not loaded yet + assign empty_secondin_o = noTD && propagate_level3; // no TDs + assign secondin_valid_o = !noTD && propagate_level3; // loaded and nonempty secondin + assign secondin_loaded_o = propagate_level3; + logic non_empty_context_switch_np2p; + assign non_empty_context_switch_np2p = secondin_valid_o && context_switch_np2p_i; + logic context_switch; + assign context_switch = context_switch_np2p_i || context_switch_p2np_i; + + // create enable, one pulse for one handshake + logic en; + logic dma_handshake; + logic dma_handshake_prev; + assign dma_handshake = dma_ready && dma_valid_i; + `FF(dma_handshake_prev, dma_handshake, 1'b0) + assign en = dma_handshake && ~dma_handshake_prev; + + // secondin registers + `FFL(dword0, dma_data_i, en, 32b'0) // Dword0 + `FFL(dword1, dword0, en, 32b'0) // Dword1 + `FFL(dword2, dword1, en, 32b'0) // Dword2 + `FFL(dword3, dword2, en, 32b'0) // Dword3 + + // secondin fill propagation + logic propagate_level0; + logic propagate_level1; + logic propagate_level2; + logic propagate_level3; + logic rst_n_propagate; + assign rst_n_propagate = !pop_i && rst_ni && !empty_secondin_o && !context_switch; // equivalent to !(pop || rst_i || empty_secondin || context_switch) + `FFL(propagate_level0, 1'b1, en, 1b'0, clk_i, rst_n_propagate) // Propagatelevel0 + `FFL(propagate_level1, propagate_level0, en, 1b'0, clk_i, rst_n_propagate) // Propagatelevel1 + `FFL(propagate_level2, propagate_level1, en, 1b'0, clk_i, rst_n_propagate) // Propagatelevel2 + `FFL(propagate_level3, propagate_level2, en, 1b'0, clk_i, rst_n_propagate) // Propagatelevel3 + assign dma_ready = !propagate_level3; + + // context switch stash + logic active_stash; + logic rst_n_stash; + logic non_empty_context_switch_p2np; + assign non_empty_context_switch_p2np = active_stash && context_switch_p2np_i; + assign rst_n_stash = !context_switch_p2np_i && rst_ni; // equivalent to !(context_switch_p2np_i || rst_i ) + `FF(active_stash, non_empty_context_switch_np2p, 1b'0, clk_i, rst_n_stash) + `FFL(stash, secondin, non_empty_context_switch_np2p, '0) // stash secondin at context switch if valid + + // create valid firstin ready for TD processing + assign pop_ready_o = secondin_valid_o || non_empty_context_switch_p2np; + `FFL(firstin_valid_o, 1'b1, pop_i, 1b'0) // firstin stays valid until next rst_ni + + // stash and secondin muxed into firstin + assign secondinmux = non_empty_context_switch_p2np ? stash : secondin; + + // The nextED address needs to be updated in firstin in case secondinED has no TD and the next ED is loaded instead. + assign firstin_nextED_overwrite = pop_i || empty_secondin_o; + `FFL(firstin.status.MPS, secondinmux.status.MPS, pop_i, '0) // firstin register + `FFL(firstin.status.F, secondinmux.status.F, pop_i, '0) // firstin register + `FFL(firstin.status.K, secondinmux.status.K, pop_i, '0) // firstin register + `FFL(firstin.status.S, secondinmux.status.S, pop_i, '0) // firstin register + `FFL(firstin.status.D, secondinmux.status.D, pop_i, '0) // firstin register + `FFL(firstin.status.EN, secondinmux.status.EN, pop_i, '0) // firstin register + `FFL(firstin.status.FA, secondinmux.status.FA, pop_i, '0) // firstin register + `FFL(firstin.headTD.address, secondinmux.headTD.address, pop_i, '0) // firstin register + `FFL(firstin.headTD.C, secondinmux.headTD.C, pop_i, '0) // firstin register + `FFL(firstin.headTD.H, secondinmux.headTD.H, pop_i, '0) // firstin register + `FFL(firstin.nextED.address, secondinmux.nextED.address, firstin_nextED_overwrite, '0) // firstin register + +endmodule \ No newline at end of file diff --git a/hw/newusb/new_usb_dmaoutputqueueTD.sv b/hw/newusb/new_usb_dmaoutputqueueTD.sv new file mode 100644 index 00000000..2eb0b895 --- /dev/null +++ b/hw/newusb/new_usb_dmaoutputqueueTD.sv @@ -0,0 +1,77 @@ +// Copyright 2024 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 +// +// Fabian Hauser +// +/// Output queue of the DMA for general transfer descriptors 4x32 bit buffering for isochronous 8x32 +/// Todo: implement ISOCHRONOUS +/// Todo: implement listservicing + +package new_usb_dmaoutputqueueTD_pkg; + + typedef struct packed { + struct packed { + logic [27:0] address; + } nextTD; + } gen_transfer_descriptor; + + typedef struct packed { + struct packed { + logic [27:0] address; + } nextTD; + } iso_transfer_descriptor; + +endpackage + +module new_usb_dmaoutputqueueTD import new_usb_dmaoutputqueueTD_pkg::*; ( + /// control + input logic clk_i, + input logic rst_ni, + /// data input + input logic [31:0] dma_data_i, + input logic dma_valid_i, + output logic dma_ready_o, + /// external TD access + output logic [27:0] nextTD_address_o, + output logic served_td_o +); + gen_transfer_descriptor general; + assign nextTD_address_o = general.nextTD; + assign served_td_o = propagate_level3; + + logic [31:0] dword0; + logic [31:0] dword1; + logic [31:0] dword2; + logic [31:0] dword3; + + assign dword0 [26:16] = general.nextTD; + + // create enable, one pulse for one handshake + logic en; + logic dma_handshake; + logic dma_handshake_prev; + assign dma_handshake = dma_ready && dma_valid_i; + `FF(dma_handshake_prev, dma_handshake, 1'b0) + assign en = dma_handshake && ~dma_handshake_prev; + + // registers + `FFL(dword0, dma_data, en, 32b'0) // Dword0 + `FFL(dword1, dword0, en, 32b'0) // Dword1 + `FFL(dword2, dword1, en, 32b'0) // Dword2 + `FFL(dword3, dword2, en, 32b'0) // Dword3 + + // fill propagation + logic propagate_level0; + logic propagate_level1; + logic propagate_level2; + logic propagate_level3; + logic rst_n_propagate; + assign rst_n_propagate = rst_ni; + `FFL(propagate_level0, 1'b1, en, 1b'0, clk_i, rst_n_propagate) // Propagatelevel0 + `FFL(propagate_level1, propagate_level0, en, 1b'0, clk_i, rst_n_propagate) // Propagatelevel1 + `FFL(propagate_level2, propagate_level1, en, 1b'0, clk_i, rst_n_propagate) // Propagatelevel2 + `FFL(propagate_level3, propagate_level2, en, 1b'0, clk_i, rst_n_propagate) // Propagatelevel3 + assign dma_ready = !propagate_level3; + +endmodule \ No newline at end of file diff --git a/hw/newusb/new_usb_listservice.sv b/hw/newusb/new_usb_listservice.sv new file mode 100644 index 00000000..e19ca17d --- /dev/null +++ b/hw/newusb/new_usb_listservice.sv @@ -0,0 +1,185 @@ +// Copyright 2024 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 +// +// Fabian Hauser +// +/// Services the lists of the four channel types and feeds the DMA with the it. + +// One ED comes into the module, one goes out of the module. No register latency. + +// Todo: implement device write back or do on different module +// add missing td +// when first TD of ED is initiated ED is served. Maybe increase counter after td and not ed. +// rewrite with listfilled +// scheduling overrun SOC +// init x axi +// HCAA + + +// Todo: implement interrupt done, back to nonperiodic +// if interrupt done do periodic_frame 0 + +module new_usb_listservice import new_usb_ohci_pkg::*; #( + //parameters +) ( + + input logic clk_i, + input logic rst_ni, + + input logic frame_periodic, // frame is currently in periodic or nonperiodic zone + input logic frame_request, // frame requests new data + + //next ED or TD and one of the four channel types + input logic nextis_valid_i // needs to be one clock cycle + input logic nextis_ed_i, + input channel nextis_type_i, + input logic [27:0] nextis_address_i, + output logic nextis_ready_o, // listservice is ready for nextis + + input logic counter_is_threshold_i, // nonperiodic counter full, switch to bulk + + output logic controlbulkratio_qe, + input logic [ 1:0] controlbulkratio_q, + + output logic periodcurrent_ed_de, + output logic [27:0] periodcurrent_ed_d, + input logic [27:0] periodcurrent_ed_q, + + output logic controlcurrent_ed_de, + output logic [27:0] controlcurrent_ed_d, + input logic [27:0] controlcurrent_ed_q, + + output logic bulkcurrent_ed_de, + output logic [27:0] bulkcurrent_ed_d, + input logic [27:0] bulkcurrent_ed_q, + + output logic hcbulkhead_ed_de, + output logic [27:0] hcbulkhead_ed_d, + input logic [27:0] hcbulkhead_ed_q, + + output logic controlhead_ed_de, + output logic [27:0] controlhead_ed_d, + input logic [27:0] controlhead_ed_q, + + output logic [27:0] nextreadwriteaddress_o, + output logic validdmaaccess_o, + output logic dmawriteorread_o, // write high, read low + output channel current_type_o, + output logic current_ed_o, + + output logic activebulkhead, + output logic activecontrolhead, + input logic activebulkheadprocessed, + input logic activecontrolheadprocessed, + input logic activeaddress, // First ED address coming from head + +); + + // listservice is not ready for nextis as long as we have an active head + assign nextis_ready_d = !(activebulkhead || activecontrolhead); + `FF(nextis_ready_o, nextis_ready_d, 1'b0) // one cycle delay to avoid concurrencies + + // Valid periodic or nonperiodic endpoint descriptor + assign nextis_valid_ed = (nextis_valid_i && nextis_ed_i); + assign nextis_valid_td = (nextis_valid_i && !nextis_ed_i); + + // Write enable + assign bulkcurrent_ed_de = ((nextis_valid_ed && (nextis_type_i == BULK)) || activebulkheadprocessed); + assign controlcurrent_ed_de = ((nextis_valid_ed && (nextis_type_i == CONTROL)) || activecontrolheadprocessed); + assign periodcurrent_ed_de = ( nextis_valid_ed && ((nextis_type_i == ISOCHRONOUS) || (nextis_type_i == INTERRUPT))); + + // Store current address if write enabled + assign periodcurrent_ed_d = nextis_address_i; + assign controlcurrent_ed_d = nextis_address_i; + assign bulkcurrent_ed_d = nextis_address_i; + + // channel scheduling + logic channel_change; + logic use_bulkhead; + logic use_controlhead; + logic clear_i; + logic next_clear; + logic en_i; + logic next_enable; + + // Flip Flop format + // `FFL(q, d, load_enable, reset_value) + + + channel_change = (counter_is_threshold_i || (nextis_type_i[1] != frame_periodic)); // CBSR or frame zone induced channel change + + // channel scheduling output address write multiplexer + // ToDo one TD each, rethink tree, list filled missing + // create maybe validdmaaccess_o FF pulse + always_comb begin + nextreadwriteaddress_o = 28'b0; + current_type_o = ISOCHRONOUS; // not important which default type + validdmaaccess_o = 1'b0; + dmawriteorread_o = 1'b0; + current_ed_o = 1'b1; + if(activebulkheadprocessed) begin // Send first BULK ED + nextreadwriteaddress_o = active_address; + current_type_o = BULK; + validdmaaccess_o = 1'b1; + // dmawriteorread_o = 1'b0; + // current_ed_o = 1'b1; + activebulkhead = 1'b0; + end + else if(activecontrolheadprocessed) begin // Send first CONTROL ED + nextreadwriteaddress_o = active_address; + current_type_o = CONTROL; + validdmaaccess_o = 1'b1; + // dmawriteorread_o = 1'b0; + // current_ed_o = 1'b1; + activecontrolhead = 1'b0; + end + else begin + if(nextis_valid_i) begin + validdmaaccess_o = 1'b1; + if(nextis_ed_i) begin + if(channel_change) begin + if(frame_periodic) begin + nextreadwriteaddress_o = periodcurrent_ed_q; + current_type_o = INTERRUPT; + // validdmaaccess_o = 1'b1; + // dmawriteorread_o = 1'b0; + // current_ed_o = 1'b1; + end + else begin + if(counter_overflown) begin + nextreadwriteaddress_o = bulkcurrent_ed_q; + current_type_o = BULK; + // validdmaaccess_o = 1'b1; + // dmawriteorread_o = 1'b0; + // current_ed_o = 1'b1; + end + else begin + nextreadwriteaddress_o = controlcurrent_ed_q; + current_type_o = CONTROL; + // validdmaaccess_o = 1'b1; + // dmawriteorread_o = 1'b0; + // current_ed_o = 1'b1; + end + end + end + else begin + nextreadwriteaddress_o = nextis_address_i; + current_type_o = nextis_type_i; // no channel change + // validdmaaccess_o = 1'b1; + // dmawriteorread_o = 1'b0; + // current_ed_o = 1'b1; + end + end + else begin + nextreadwriteaddress_o = nextis_address_i; + current_type_o = nextis_type_i; // no channel change + // validdmaaccess_o = 1'b1; + // dmawriteorread_o = 1'b0; + current_ed_o = 1'b0; + end + end + end + end + +endmodule diff --git a/hw/newusb/new_usb_nonperiodiccounter.sv b/hw/newusb/new_usb_nonperiodiccounter.sv new file mode 100644 index 00000000..472fb2c2 --- /dev/null +++ b/hw/newusb/new_usb_nonperiodiccounter.sv @@ -0,0 +1,51 @@ +// Copyright 2024 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 +// +// Fabian Hauser +// +/// nonperiodic scheduling counter + +module new_usb_nonperiodiccounter( + + input logic clk_i, + input logic rst_ni, + input logic served_bulk_td_i, // successfully served bulk transfer descriptor + input logic served_control_td_i, // successfully served control transfer descriptor + input logic [1:0] cbsr_i, + + output logic counter_overflown_o, // enough control EDs served + output logic counter_is_threshold_o // signals last control ED to send for listservice + +); + + logic [1:0] count; + logic restart_counter; + + counter #(.WIDTH(2), .STICKY_OVERFLOW(1'b1)) i_counter ( + .clk_i, + .rst_ni, + .clear_i(1'b0), + .en_i, + .load_i(reload_cbsr), // only load CBSR as max value during restart_counter or reset + .down_i(1'b1), + .d_i(cbsr_i), + .q_o(count), + .overflow_o(counter_overflown_o) + ); + + assign counter_is_threshold_i = (count == 2'b00); + + // create enable, one pulse for one count + logic served_control_td_prev; + `FF(served_control_td_prev, served_control_td_i, 1'b0) + assign en_i = served_control_td_i && ~served_control_td_prev; + + // create reload, one pulse for one reload + logic served_bulk_td_prev; + logic reloadcbsr; + `FF(served_bulk_td_prev, served_bulk_td_i, 1'b0) + restart_counter = served_bulk_td_i && ~served_bulk_td_prev; + assign reload_cbsr = (restart_counter || !rst_ni); + +endmodule \ No newline at end of file diff --git a/hw/newusb/new_usb_ohci.sv b/hw/newusb/new_usb_ohci.sv index d0a76857..f2218a64 100644 --- a/hw/newusb/new_usb_ohci.sv +++ b/hw/newusb/new_usb_ohci.sv @@ -1,17 +1,15 @@ // Copyright 2024 ETH Zurich and University of Bologna. // Solderpad Hardware License, Version 0.51, see LICENSE for details. // SPDX-License-Identifier: SHL-0.51 - +// // Fabian Hauser - +// /// Main module for the direct SystemVerilog NewUSB OHCI, configured for AXI4 buses. /// The config port is adapted to 32b Regbus, the DMA port to parametric AXI4. /// The IOs are bundled into PULP structs and arrays to simplify connection. - // Changes inside this package need to be confirmed with a make hw-all, // because the package values need to update the configuration inside newusb_regs.hjson. -// Always delete the previous newusb_regs.hjson first, if you do changes here. package new_usb_ohci_pkg; typedef enum int unsigned { @@ -25,16 +23,29 @@ package new_usb_ohci_pkg; ENABLE = 1 } state_permit; - //OHCI supports between 1-15 ports + typedef enum logic [1:0] { + BULK = 2'b00, + CONTROL = 2'b01, + ISOCHRONOUS = 2'b10, + INTERRUPT = 2'11 + } channel; + + typedef enum logic [1:0] { + ED = 2'b00, + GENTD = 2'b01, + ISOTD = 2'b10 + } store_type; + + + // OHCI supports between 1-15 ports localparam int unsigned NumPhyPorts = 2; localparam state_activate OverProtect = OFF; // no overcurrent protection implemented yet localparam state_activate PowerSwitching = OFF; // no power switching implemented yet localparam state_permit InterruptRouting = DISABLE; // no system management interrupt (SMI) implemented yet localparam state_permit RemoteWakeup = DISABLE; // no remote wakeup implemented yet localparam state_permit OwnershipChange = DISABLE; // no ownership change implemented yet - localparam int unsigned FifodepthPort = 1024; //test value - localparam int unsigned FifodepthDma = 1024; //test value - localparam int unsigned Dmalength = 1024; //test value + localparam int unsigned FifodepthPort = 1024; // test value + localparam int unsigned Dmalength = 128; // test value // Todo: Maybe Crc16 input Byte size parameter with selectable parallel/pipelined processing, lookup table? @@ -80,30 +91,127 @@ module new_usb_ohci import new_usb_ohci_pkg::*; #( output logic [NumPhyPorts-1:0] phy_dp_oe_o ); -newusb_reg_pkg::newusb_hw2reg_t newusb_hw2reg; -newusb_reg_pkg::newusb_reg2hw_t newusb_reg2hw; - -newusb_reg_top #( - .reg_req_t ( reg_req_t ), - .reg_rsp_t ( reg_rsp_t ) -) i_newusb_regs ( - .clk_i ( soc_clk_i ), - .rst_ni ( soc_rst_ni ), - .reg_req_i ( ctrl_req_i ), //SW HCD - .reg_rsp_o ( ctrl_rsp_o ), //SW HCD - .reg2hw ( newusb_reg2hw ), //HW HC to Reg - .hw2reg ( newusb_hw2reg ), //HW Reg to HC - .devmode_i ( 1'b1 ) + newusb_reg_pkg::newusb_hw2reg_t newusb_hw2reg; + newusb_reg_pkg::newusb_reg2hw_t newusb_reg2hw; + + newusb_reg_top #( + .reg_req_t ( reg_req_t ), + .reg_rsp_t ( reg_rsp_t ) + ) i_newusb_regs ( + .clk_i ( soc_clk_i ), + .rst_ni ( soc_rst_ni ), + .reg_req_i ( ctrl_req_i ), // SW HCD + .reg_rsp_o ( ctrl_rsp_o ), // SW HCD + .reg2hw ( newusb_reg2hw ), // HW HC to Reg + .hw2reg ( newusb_hw2reg ), // HW Reg to HC + .devmode_i ( 1'b1 ) + ); + + // frame periodic/nonperiodic and its transition pulses + logic frame_periodic; // 0 if nonperiodic 1 if periodic + logic frame_periodic_prev; + logic context_switch_np2p; + logic context_switch_p2np; + `FF(frame_periodic_prev, frame_periodic, 1'b0) + assign context_switch_np2p = frame_periodic && ~frame_periodic_prev; // one cycle high nonperiodic to periodic + assign context_switch_n2np = ~frame_periodic && frame_periodic_prev; // one cyble high periodic to nonperiodic + + // listservice + logic start; // start if USB goes to operational + logic frame_request; + logic nextis_valid; + logic nextis_ed; + channel nextis_type; + logic [27:0] nextis_address; + + new_usb_listservice i_listservice ( + + .clk_i(soc_clk_i), + .rst_i(soc_rst_ni), + + .start(start), + .frame_periodic(frame_periodic), + + .nextis_valid(nextis_valid), + .nextis_ed(nextis_ed), + .nextis_type(nextis_type), + .nextis_address(nextis_address), + + .controlbulkratio_q(reg2hw.hccontrol.cbsr.q), + + .periodcurrent_ed_de(newusb_hw2reg.hcperiodcurrented.pced.de), + .periodcurrent_ed_d(newusb_hw2reg.hcperiodcurrented.pced.d), + .periodcurrent_ed_q(newusb_reg2hw.hcperiodcurrented.pced.q), + + .controlcurrent_ed_de(newusb_hw2reg.hccontrolcurrented.cced.de), + .controlcurrent_ed_d(newusb_hw2reg.hccontrolcurrented.cced.d), + .controlcurrent_ed_q(newusb_reg2hw.hccontrolcurrented.cced.q), + + .bulkcurrent_ed_de(newusb_hw2reg.hcbulkcurrented.bced.de), + .bulkcurrent_ed_d(newusb_hw2reg.hcbulkcurrented.bced.d), + .bulkcurrent_ed_q(newusb_reg2hw.hcbulkcurrented.bced.q), + + .hcbulkhead_ed_de(newusb_hw2reg.hcbulkheaded.bhed.de), + .hcbulkhead_ed_d(newusb_hw2reg.hcbulkheaded.bhed.d) , + .hcbulkhead_ed_q(newusb_reg2hw.hcbulkheaded.bhed.q), + + .controlhead_ed_de(newusb_hw2reg.hccontrolheaded.ched.de), + .controlhead_ed_d(newusb_hw2reg.hccontrolheaded.ched.d) , + .controlhead_ed_q(newusb_reg2hw.hccontrolheaded.ched.q), + + .nextreadwriteaddress(), + .validdmaaccess(), + .current_type(), + .sent_head() + + ); + + new_usb_unpackdescriptors i_new_usb_unpackdescriptors ( + .cbsr_i(reg2hw.hccontrol.cbsr.q), + ) + module new_usb_unpackdescriptors import new_usb_ohci_pkg::*;( + /// control + .clk_i, + .rst_ni, + .counter_is_threshold_o, + + .nextis_valid_o // needs to be one clock cycle + .nextis_ed_o, // 0 if empty ed rerequest or td + .nextis_type_o, + .nextis_address_o, + .nextis_ready_i, + + .processed, + .processed_ed_store_o, // store request + .processed_store_type_o, // isochronousTD, generalTD, ED + + .newcurrentED_o, + .newcurrentED_valid_o, + + .id_valid_i, + .id_type_i, + + .dma_data_i, + .dma_valid_i, + .dma_ready_o, + + .context_switch_np2p_i, + .context_switch_p2np_i, + + .sent_head_i ); + -assign dma_req_o = '0; -// IRQ tied-off -assign intr_o = '0; + // Todo: insert DMA -assign phy_dm_o = '0; -assign phy_dm_oe_o = '0; -assign phy_dp_o = '0; -assign phy_dp_oe_o = '0; + assign dma_req_o = '0; + // IRQ tied-off + assign intr_o = '0; + + assign phy_dm_o = '0; + assign phy_dm_oe_o = '0; + assign phy_dp_o = '0; + assign phy_dp_oe_o = '0; endmodule diff --git a/hw/newusb/new_usb_unpackdescriptors.sv b/hw/newusb/new_usb_unpackdescriptors.sv new file mode 100644 index 00000000..5ab50231 --- /dev/null +++ b/hw/newusb/new_usb_unpackdescriptors.sv @@ -0,0 +1,196 @@ +// Copyright 2024 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 +// +// Fabian Hauser +// +/// Unpacks the data fields from the descriptors, whose addresses the DMA received from listservice, +/// and sends the next linked addresses back to listservice. +/// The address field inside the secondinED in the two-stage queue is sent to listservice. +/// The address of the first ED gets written into the respective current ED register as +/// soon it starts being processed. +/// The module takes firstinED in the queue and sends its first TD address to listservice. +/// After processing the TD, it proceds to the next ED. + + +// Todo: only one package flying +// Todo: TD management in ED, overwrite HeadP if servedTD, halted or toggle Carry? + +module new_usb_unpackdescriptors import new_usb_ohci_pkg::*;( + /// control + input logic clk_i, + input logic rst_ni, + input logic [1:0] cbsr_i, + output logic counter_is_threshold_o, + /// next address element + output logic nextis_valid_o // needs to be one clock cycle + output logic nextis_ed_o, // 0 if empty ed rerequest or td + output channel nextis_type_o, + output logic [27:0] nextis_address_o, + input logic nextis_ready_i, + /// Processed ED with data to write back, get address from currentED + output new_usb_dmaoutputqueueED_pkg::endpoint_descriptor processed, + output logic processed_ed_store_o, // store request + output store_type processed_store_type_o, // isochronousTD, generalTD, ED + /// new currentED, updated after processed accessed it + output logic [27:0] newcurrentED_o, + output logic newcurrentED_valid_o, + /// dma data + input logic [1:0] id_valid_i, + input logic [2:0] id_type_i, + /// dma data + input logic [31:0] dma_data_i, + input logic dma_valid_i, + output logic dma_ready_o, + /// periodic, nonperiodic transitions + input logic context_switch_np2p_i, + input logic context_switch_p2np_i, + /// head state + input logic sent_head_i +); + // nextis + assign nextis_ed_o = (id_type[2] && empty_secondin) || !id_type[2]; + assign nextis_valid_o = !flying; + assign nextis_type = id_type[1:0]; + assign nextis_address_o = secondin.nextED.address; + + // 3 bit ID stack td or ed and channel type + logic id_en; + logic id_valid_i_prev; + logic [2:0] id_type_flying; + logic [2:0] id_type_secondin; + logic [2:0] id_type_firstin; + `FFL(id_type_flying, id_type_i, id_en, 3b'101) // ID type flying, sent into dma but not out yet + `FFL(id_type_secondin, id_type_flying, id_en, 3b'101) // ID type secondin + `FFL(id_type_firstin, id_type_secondin, id_en, 3b'101) // ID type firstin + `FF(id_valid_i_prev, id_valid, 1'b0) + assign id_en = id_valid_i && ~id_valid_i_prev; + + /// Exit sequence: served_td -> processed -> newcurrentED -> pop + // processed + assign processed.headTD.address = nextTD; + assign processed_store_type_o = ED; // Todo:derive from ID stack firstin + assign processed_ed_store_o = pop_very_early; + // Todo: halt + // Todo: skip + + // new currentED + assign newcurrentED_o = firstin.nextED.address; + assign newcurrentED_valid_o = pop_early; + + // control bulk ratio counter + logic served_td; + logic served_bulk_td; + logic served_control_td; + logic counter_overflown; + assign served_td && (id_type_firstin == BULK); + assign served_td && (id_type_firstin == CONTROL); + + new_usb_nonperiodiccounter i_nonperiodiccounter ( + .clk_i, + .rst_ni, + .served_bulk_td_i(served_bulk_td), + .served_control_td_i(served_control_td), + .cbsr_i, + .counter_overflown_o(counter_overflown), + .counter_is_threshold_o + ); + + // dma data path selector (ed | td | flush) + assign dma_ready_o = dma_ready_ed || dma_ready_td || dma_flush; + assign dma_valid_ed = dma_valid_i && ed && !dma_flush; + assign dma_valid_td = dma_valid_i && !ed && !dma_flush; + + // dma flush, early flush to prevent stage loading, save power and increase speed + logic dma_flush; + logic dma_flush_en; + logic flush_level0; + logic flush_level1; + logic flush_level2; + logic flush_level3; + logic rst_n_flush; + logic rst_n_dma_flush; + assign dma_flush_en = doublehead_invalid; // Todo: add other flush reasons + assign rst_n_flush = dma_flush && rst_ni; // equivalent to !(!dma_flush || rst_i ) + assign rst_n_dma_flush = !flush_level3; // equivalent to !(flush_level3) + `FFL(flush_level0, 1'b1, flush_en, 1b'0, clk_i, rst_n_flush) // flushlevel0 + `FFL(flush_level1, flush_level0, flush_en, 1b'0, clk_i, rst_n_flush) // flushlevel1 + `FFL(flush_level2, flush_level1, flush_en, 1b'0, clk_i, rst_n_flush) // flushlevel2 + `FFL(flush_level3, flush_level2, flush_en, 1b'0, clk_i, rst_n_flush) // flushlevel3 + `FFL(dma_flush, 1'b1, dma_flush_en, 1b'0, clk_i, rst_n_dma_flush) // dma_flush + + // note: could maybe used as one with twin inside outputqueueED + // create flush enable, one pulse for one handshake + logic flush_en; + logic dma_flush_handshake; + logic dma_flush_handshake_prev; + assign dma_flush_handshake = dma_flush && dma_valid_i; + `FF(dma_flush_handshake_prev, dma_flush_handshake, 1'b0) + assign flush_en = dma_flush_handshake && ~dma_flush_handshake_prev; + + // generate pop one cycle delayed to served_td + logic pop_handshake; + logic pop_handshake_prev; + assign pop_handshake = pop_ready && served_td && nextis_ready_i; + `FF(pop_handshake_prev, pop_handshake, 1'b0) + assign pop_very_early = pop_handshake && ~pop_handshake_prev; + `FF(pop_early, pop_very_early, 1'b0) + `FF(pop, pop_early, 1'b0) + + // dma output queue endpoint descriptor + logic pop; + logic pop_ready; + logic empty_secondin; + logic firstin_valid; + logic secondin_valid; + logic secondin_loaded; + logic dma_valid_ed; + logic dma_ready_ed; + + new_usb_dmaoutputqueueED_pkg::endpoint_descriptor firstin; + new_usb_dmaoutputqueueED_pkg::endpoint_descriptor secondin; + + // Todo: context switch with active stash get address from firstin.nextED.address and generally when secondin not valid/empty + new_usb_dmaoutputqueueED i_dmaoutputqueueED ( + /// control + .clk_i, + .rst_ni, + .pop_i(pop), // @pop store currenthead and do stash or secondin -> firstin + .pop_ready_o(pop_ready), // stash or secondin ready for loading into firstin + .context_switch_np2p_i, // nonperiodic to periodic + .context_switch_p2np_i, // periodic to nonperiodic + .empty_secondin_o(empty_secondin), // request new ED with secondin.nextED.address + .firstin_valid_o(firstin_valid), + .secondin_valid_o(secondin_valid), // only valid if TDs inside secondinED + .secondin_loaded_i(secondin_loaded), // secondinED loaded + /// data input + .dma_data_i, + .dma_valid_i(dma_valid_ed), + .dma_ready_o(dma_ready_ed), + /// external ED access + .secondin, + .firstin + ); + + // validity doublehead check + logic loaded_head; + logic doublehead_invalid; + assign loaded_head = (currentED == headED) && secondin_loaded; + assign doublehead_invalid = loaded_head && ((headED + 128) != secondin.nextED.address); + + // dma output queue transfer descriptor + logic [27:0] nextTD; + new_usb_dmaoutputqueueTD i_dmaoutputqueueTD ( + /// control + .clk_i, + .rst_ni, + /// data input + .dma_data_i(dma_data_i), + .dma_valid_i(dma_valid_td), + .dma_ready_o(dma_ready_td), + /// external TD access + .nextTD_address_o(nextTD), + .served_td_o(served_td) + ); + +endmodule \ No newline at end of file diff --git a/hw/newusb_regs/newusb_insert_param.py b/hw/newusb_regs/newusb_insert_param.py index 42f3a453..4c463066 100755 --- a/hw/newusb_regs/newusb_insert_param.py +++ b/hw/newusb_regs/newusb_insert_param.py @@ -1,7 +1,6 @@ # Copyright 2022 ETH Zurich and University of Bologna. # Solderpad Hardware License, Version 0.51, see LICENSE for details. # SPDX-License-Identifier: SHL-0.51 -# Licensed under Solderpad Hardware License, Version 0.51, see LICENSE for details. # # Fabian Hauser # diff --git a/hw/newusb_regs/newusb_reg_pkg.sv b/hw/newusb_regs/newusb_reg_pkg.sv index a449cc78..5e4211be 100644 --- a/hw/newusb_regs/newusb_reg_pkg.sv +++ b/hw/newusb_regs/newusb_reg_pkg.sv @@ -503,9 +503,6 @@ package newusb_reg_pkg; struct packed { logic d; } rhcs; - struct packed { - logic [22:0] d; - } reserved; struct packed { logic d; } oc; @@ -801,11 +798,11 @@ package newusb_reg_pkg; // HW -> register type typedef struct packed { - newusb_hw2reg_hccontrol_reg_t hccontrol; // [718:708] - newusb_hw2reg_hccommandstatus_reg_t hccommandstatus; // [707:676] - newusb_hw2reg_hcinterruptstatus_reg_t hcinterruptstatus; // [675:644] - newusb_hw2reg_hcinterruptenable_reg_t hcinterruptenable; // [643:612] - newusb_hw2reg_hcinterruptdisable_reg_t hcinterruptdisable; // [611:580] + newusb_hw2reg_hccontrol_reg_t hccontrol; // [695:685] + newusb_hw2reg_hccommandstatus_reg_t hccommandstatus; // [684:653] + newusb_hw2reg_hcinterruptstatus_reg_t hcinterruptstatus; // [652:621] + newusb_hw2reg_hcinterruptenable_reg_t hcinterruptenable; // [620:589] + newusb_hw2reg_hcinterruptdisable_reg_t hcinterruptdisable; // [588:580] newusb_hw2reg_hchcaa_reg_t hchcaa; // [579:555] newusb_hw2reg_hcperiodcurrented_reg_t hcperiodcurrented; // [554:526] newusb_hw2reg_hccontrolheaded_reg_t hccontrolheaded; // [525:497] @@ -898,7 +895,6 @@ package newusb_reg_pkg; parameter logic [0:0] NEWUSB_HCINTERRUPTDISABLE_UE_RESVAL = 1'h 0; parameter logic [0:0] NEWUSB_HCINTERRUPTDISABLE_FNO_RESVAL = 1'h 0; parameter logic [0:0] NEWUSB_HCINTERRUPTDISABLE_RHCS_RESVAL = 1'h 0; - parameter logic [22:0] NEWUSB_HCINTERRUPTDISABLE_RESERVED_RESVAL = 23'h 0; parameter logic [0:0] NEWUSB_HCINTERRUPTDISABLE_OC_RESVAL = 1'h 0; parameter logic [0:0] NEWUSB_HCINTERRUPTDISABLE_MIE_RESVAL = 1'h 0; diff --git a/hw/newusb_regs/newusb_reg_top.sv b/hw/newusb_regs/newusb_reg_top.sv index 9d32beb1..a1a2b561 100644 --- a/hw/newusb_regs/newusb_reg_top.sv +++ b/hw/newusb_regs/newusb_reg_top.sv @@ -229,8 +229,6 @@ module newusb_reg_top #( logic hcinterruptdisable_rhcs_wd; logic hcinterruptdisable_rhcs_we; logic hcinterruptdisable_rhcs_re; - logic [22:0] hcinterruptdisable_reserved_qs; - logic hcinterruptdisable_reserved_re; logic hcinterruptdisable_oc_qs; logic hcinterruptdisable_oc_wd; logic hcinterruptdisable_oc_we; @@ -1068,21 +1066,6 @@ module newusb_reg_top #( ); - // F[reserved]: 29:7 - prim_subreg_ext #( - .DW (23) - ) u_hcinterruptdisable_reserved ( - .re (hcinterruptdisable_reserved_re), - .we (1'b0), - .wd ('0), - .d (hw2reg.hcinterruptdisable.reserved.d), - .qre (), - .qe (), - .q (), - .qs (hcinterruptdisable_reserved_qs) - ); - - // F[oc]: 30:30 prim_subreg_ext #( .DW (1) @@ -3151,8 +3134,6 @@ module newusb_reg_top #( assign hcinterruptdisable_rhcs_wd = reg_wdata[6]; assign hcinterruptdisable_rhcs_re = addr_hit[5] & reg_re & !reg_error; - assign hcinterruptdisable_reserved_re = addr_hit[5] & reg_re & !reg_error; - assign hcinterruptdisable_oc_we = addr_hit[5] & reg_we & !reg_error; assign hcinterruptdisable_oc_wd = reg_wdata[30]; assign hcinterruptdisable_oc_re = addr_hit[5] & reg_re & !reg_error; @@ -3363,7 +3344,6 @@ module newusb_reg_top #( reg_rdata_next[4] = hcinterruptdisable_ue_qs; reg_rdata_next[5] = hcinterruptdisable_fno_qs; reg_rdata_next[6] = hcinterruptdisable_rhcs_qs; - reg_rdata_next[29:7] = hcinterruptdisable_reserved_qs; reg_rdata_next[30] = hcinterruptdisable_oc_qs; reg_rdata_next[31] = hcinterruptdisable_mie_qs; end diff --git a/hw/newusb_regs/newusb_regs_template.hjson b/hw/newusb_regs/newusb_regs_template.hjson index a3a6bb7a..8e3cc55a 100644 --- a/hw/newusb_regs/newusb_regs_template.hjson +++ b/hw/newusb_regs/newusb_regs_template.hjson @@ -1,7 +1,6 @@ // Copyright 2022 ETH Zurich and University of Bologna. // Solderpad Hardware License, Version 0.51, see LICENSE for details. // SPDX-License-Identifier: SHL-0.51 -// Licensed under Solderpad Hardware License, Version 0.51, see LICENSE for details. // // Fabian Hauser // @@ -458,11 +457,11 @@ { value: "1", name: "disable_gen", desc: "Disable OwnershipChange interrupt generation" } ] } - { bits: "29:7" - name: "Reserved" - desc: "Reserved" - resval: "0" - } + // { bits: "29:7" + // name: "Reserved" + // desc: "Reserved" + // resval: "0" + // } { bits: "6" name: "RHCS" desc: "RootHubStatusChange"