def __init__(self): super().__init__() self.reg_fifo_items_value = Signal(32) self.reg_verify_value = Signal(32) self.output_words = Endpoint(unsigned(32))
def __init__(self, *, bus, handle_clocking=True): """ Parameters: """ # If this looks more like a ULPI bus than a UTMI bus, translate it. if hasattr(bus, 'dir'): self.utmi = UTMITranslator(ulpi=bus, handle_clocking=handle_clocking) self.bus_busy = self.utmi.busy self.translator = self.utmi self.always_fs = False self.data_clock = 60e6 # If this looks more like raw I/O connections than a UTMI bus, create a pure-gatware # PHY to drive the raw I/O signals. elif hasattr(bus, 'd_n'): self.utmi = GatewarePHY(io=bus) self.bus_busy = Const(0) self.translator = self.utmi self.always_fs = True self.data_clock = 12e6 # Otherwise, use it directly. # Note that since a true UTMI interface has separate Tx/Rx/control # interfaces, we don't need to care about bus 'busyness'; so we'll # set it to a const zero. else: self.utmi = bus self.bus_busy = Const(0) self.translator = None self.always_fs = True self.data_clock = 12e6 # # I/O port # self.connect = Signal() self.low_speed_only = Signal() self.full_speed_only = Signal() self.frame_number = Signal(11) self.microframe_number = Signal(3) self.sof_detected = Signal() self.new_frame = Signal() self.reset_detected = Signal() self.speed = Signal(2) self.suspended = Signal() self.tx_activity_led = Signal() self.rx_activity_led = Signal() # # Internals. # self._endpoints = []
class PixelAddressRepeater(SimpleElaboratable): """Repeats addresses from PixelAddressGenerator. The systolic array requires multiple passes over input data. This class provides that functionatlity by repeating the pixel start addresses generated from PixelAddress Generator. Pixel addresses are repeated in groups of four, which matches the number of outputs of the InputFetcher and inputs to the systolic array. Attributes ---------- repeats: Signal(unsigned_upto(64)), in The number of times each stream of input pixel data is to be repeated. next: Signal(), in Indicates current address has been used. Address will be updated on next cycle with next row address. addr: Signal(14), out Output address start: Signal(), in Starts address generation. Addr will be updated on next cycle. gen_next: Signal(), out Indicates that a new pixel address is required. gen_addr: Signal(14), in Input from the PixelAddressGenerator """ def __init__(self): self.repeats = Signal(unsigned_upto(64)) self.next = Signal() self.addr = Signal(14) self.start = Signal() self.gen_next = Signal() self.gen_addr = Signal(14) def elab(self, m): group = Signal(range(4)) repeat_count = Signal.like(self.repeats) mem = Array([Signal(14, name=f"mem{i}") for i in range(4)]) with m.If(repeat_count == 0): # On first repeat - pass through next and addr and record addresses m.d.comb += self.gen_next.eq(self.next) m.d.comb += self.addr.eq(self.gen_addr) with m.If(self.next): m.d.sync += mem[group].eq(self.gen_addr) with m.Else(): # Subsequently use recorded data m.d.comb += self.addr.eq(mem[group]) # Update group and repeat_count on next with m.If(self.next): m.d.sync += group.eq(group + 1) with m.If(group == 3): m.d.sync += repeat_count.eq(repeat_count + 1) with m.If(repeat_count + 1 == self.repeats): m.d.sync += repeat_count.eq(0) with m.If(self.start): m.d.sync += repeat_count.eq(0) m.d.sync += group.eq(0)
def __init__(self): self.a = Signal(signed(16)) self.b = Signal(signed(16)) self.c = Signal(signed(32))
class USBDevice(Elaboratable): """ Core gateware common to all LUNA USB2 devices. The ``USBDevice`` module contains the low-level communications hardware necessary to implement a USB device; including hardware for maintaining device state, detecting events, reading data from the host, and generating responses. This class can be instantiated directly, and used to build a USB device, or can be subclassed to create custom device types. To configure a ``USBDevice`` from a CPU or other wishbone master, see :class:`USBDeviceController`; which can easily be attached using its `attach` method. Parameters ---------- bus: [UTMI interface, ULPI Interface] The UTMI or ULPI PHY connection to be used for communications. handle_clocking: bool, Optional True iff we should attempt to connect up the `usb` clock domain to the PHY automatically based on the clk signals's I/O direction. This option may not work for non-simple connections; in which case you will need to connect the clock signal yourself. Attributes ---------- connect: Signal(), input Held high to keep the current USB device connected; or held low to disconnect. low_speed_only: Signal(), input If high, the device will operate at low speed. full_speed_only: Signal(), input If high, the device will be prohibited from operating at high speed. frame_number: Signal(11), output The current USB frame number. microframe_number: Signal(3), output The current USB microframe number. Always 0 on non-HS connections. sof_detected: Signal(), output Pulses for one cycle each time a SOF is detected; and thus our frame number has changed. new_frame: Signal(), output Strobe that indicates a new frame (not microframe) is detected. reset_detected: Signal(), output Asserted when the USB device receives a bus reset. # State signals. suspended: Signal(), output High when the device is in USB suspend. This can be (and by the spec must be) used to trigger the device to enter lower-power states. tx_activity_led: Signal(), output Signal that can be used to drive an activity LED for TX. rx_activity_led: Signal(), output Signal that can be used to drive an activity LED for RX. """ def __init__(self, *, bus, handle_clocking=True): """ Parameters: """ # If this looks more like a ULPI bus than a UTMI bus, translate it. if hasattr(bus, 'dir'): self.utmi = UTMITranslator(ulpi=bus, handle_clocking=handle_clocking) self.bus_busy = self.utmi.busy self.translator = self.utmi self.always_fs = False self.data_clock = 60e6 # If this looks more like raw I/O connections than a UTMI bus, create a pure-gatware # PHY to drive the raw I/O signals. elif hasattr(bus, 'd_n'): self.utmi = GatewarePHY(io=bus) self.bus_busy = Const(0) self.translator = self.utmi self.always_fs = True self.data_clock = 12e6 # Otherwise, use it directly. # Note that since a true UTMI interface has separate Tx/Rx/control # interfaces, we don't need to care about bus 'busyness'; so we'll # set it to a const zero. else: self.utmi = bus self.bus_busy = Const(0) self.translator = None self.always_fs = True self.data_clock = 12e6 # # I/O port # self.connect = Signal() self.low_speed_only = Signal() self.full_speed_only = Signal() self.frame_number = Signal(11) self.microframe_number = Signal(3) self.sof_detected = Signal() self.new_frame = Signal() self.reset_detected = Signal() self.speed = Signal(2) self.suspended = Signal() self.tx_activity_led = Signal() self.rx_activity_led = Signal() # # Internals. # self._endpoints = [] def add_endpoint(self, endpoint): """ Adds an endpoint interface to the device. Parameters ---------- endpoint: Elaborateable The endpoint interface to be added. Can be any piece of gateware with a :class:`EndpointInterface` attribute called ``interface``. """ self._endpoints.append(endpoint) def add_control_endpoint(self): """ Adds a basic control endpoint to the device. Does not add any request handlers. If you want standard request handlers; :attr:`add_standard_control_endpoint` automatically adds standard request handlers. Returns ------- Returns the endpoint object for the control endpoint. """ control_endpoint = USBControlEndpoint(utmi=self.utmi) self.add_endpoint(control_endpoint) return control_endpoint def add_standard_control_endpoint(self, descriptors: DeviceDescriptorCollection, **kwargs): """ Adds a control endpoint with standard request handlers to the device. Parameters will be passed on to StandardRequestHandler. Return value ------------ The endpoint object created. """ # Create our endpoint, and add standard descriptors to it. control_endpoint = USBControlEndpoint(utmi=self.utmi) control_endpoint.add_standard_request_handlers(descriptors, **kwargs) self.add_endpoint(control_endpoint) return control_endpoint def elaborate(self, platform): m = Module() # If we have a bus translator, include it in our submodules. if self.translator: m.submodules.translator = self.translator # # Internal device state. # # Stores the device's current address. Used to identify which packets are for us. address = Signal(7, reset=0) # Stores the device's current configuration. Defaults to unconfigured. configuration = Signal(8, reset=0) # # Internal interconnections. # # Create our reset sequencer, which will be in charge of detecting USB port resets, # detecting high-speed hosts, and communicating that we are a high speed device. m.submodules.reset_sequencer = reset_sequencer = USBResetSequencer() m.d.comb += [ reset_sequencer.bus_busy.eq(self.bus_busy), reset_sequencer.vbus_connected.eq(~self.utmi.session_end), reset_sequencer.line_state.eq(self.utmi.line_state), ] # Create our internal packet components: # - A token detector, which will identify and parse the tokens that start transactions. # - A data transmitter, which will transmit provided data streams. # - A data receiver, which will receive data from UTMI and convert it into streams. # - A handshake generator, which will assist in generating response packets. # - A handshake detector, which detects handshakes generated by the host. # - A data CRC16 handler, which will compute data packet CRCs. # - An interpacket delay timer, which will enforce interpacket delays. m.submodules.token_detector = token_detector = \ USBTokenDetector(utmi=self.utmi, domain_clock=self.data_clock, fs_only=self.always_fs) m.submodules.transmitter = transmitter = USBDataPacketGenerator() m.submodules.receiver = receiver = USBDataPacketReceiver( utmi=self.utmi) m.submodules.handshake_generator = handshake_generator = USBHandshakeGenerator( ) m.submodules.handshake_detector = handshake_detector = USBHandshakeDetector( utmi=self.utmi) m.submodules.data_crc = data_crc = USBDataPacketCRC() m.submodules.timer = timer = \ USBInterpacketTimer(domain_clock=self.data_clock, fs_only=self.always_fs) # Connect our transmitter/receiver to our CRC generator. data_crc.add_interface(transmitter.crc) data_crc.add_interface(receiver.data_crc) # Connect our receiver to our timer. timer.add_interface(receiver.timer) m.d.comb += [ # Ensure our token detector only responds to tokens addressed to us. token_detector.address.eq(address), # Hook up our data_crc to our receive inputs. data_crc.rx_data.eq(self.utmi.rx_data), data_crc.rx_valid.eq(self.utmi.rx_valid), # Connect our state signals to our subordinate components. token_detector.speed.eq(self.speed), timer.speed.eq(self.speed) ] # # Endpoint connections. # # Create our endpoint multiplexer... m.submodules.endpoint_mux = endpoint_mux = USBEndpointMultiplexer() endpoint_collection = endpoint_mux.shared # Connect our timer and CRC interfaces. timer.add_interface(endpoint_collection.timer) data_crc.add_interface(endpoint_collection.data_crc) m.d.comb += [ # Low-level hardware interface. token_detector.interface.connect(endpoint_collection.tokenizer), handshake_detector.detected.connect( endpoint_collection.handshakes_in), # Device state. endpoint_collection.speed.eq(self.speed), endpoint_collection.active_config.eq(configuration), endpoint_collection.active_address.eq(address), # Receive interface. receiver.stream.connect(endpoint_collection.rx), endpoint_collection.rx_complete.eq(receiver.packet_complete), endpoint_collection.rx_invalid.eq(receiver.crc_mismatch), endpoint_collection.rx_ready_for_response.eq( receiver.ready_for_response), endpoint_collection.rx_pid_toggle.eq(receiver.active_pid[3]), # Transmit interface. endpoint_collection.tx.attach(transmitter.stream), handshake_generator.issue_ack.eq( endpoint_collection.handshakes_out.ack), handshake_generator.issue_nak.eq( endpoint_collection.handshakes_out.nak), handshake_generator.issue_stall.eq( endpoint_collection.handshakes_out.stall), transmitter.data_pid.eq(endpoint_collection.tx_pid_toggle), ] # If an endpoint wants to update our address or configuration, accept the update. with m.If(endpoint_collection.address_changed): m.d.usb += address.eq(endpoint_collection.new_address) with m.If(endpoint_collection.config_changed): m.d.usb += configuration.eq(endpoint_collection.new_config) # Finally, add each of our endpoints to this module and our multiplexer. for endpoint in self._endpoints: # Create a display name for the endpoint... name = endpoint.__class__.__name__ if hasattr(m.submodules, name): name = f"{name}_{id(endpoint)}" # ... and add it, both as a submodule and to our multiplexer. endpoint_mux.add_interface(endpoint.interface) m.submodules[name] = endpoint # # Transmitter multiplexing. # # Create a multiplexer that will arbitrate access to the transmit lines. m.submodules.tx_multiplexer = tx_multiplexer = UTMIInterfaceMultiplexer( ) # Connect each of our transmitters. tx_multiplexer.add_input(reset_sequencer.tx) tx_multiplexer.add_input(transmitter.tx) tx_multiplexer.add_input(handshake_generator.tx) m.d.comb += [ # Connect our transmit multiplexer to the actual UTMI bus. tx_multiplexer.output.attach(self.utmi), # Connect up the transmit CRC interface to our UTMI bus. data_crc.tx_valid.eq(tx_multiplexer.output.valid & self.utmi.tx_ready), data_crc.tx_data.eq(tx_multiplexer.output.data), ] # # Device-state management. # # On a bus reset, clear our address and configuration. with m.If(reset_sequencer.bus_reset): m.d.usb += [ address.eq(0), configuration.eq(0), ] # Device operating state controls. m.d.comb += [ # Disable our host-mode pulldowns; as we're a device. self.utmi.dm_pulldown.eq(0), self.utmi.dp_pulldown.eq(0), # Let our reset sequencer set our USB mode and speed. reset_sequencer.low_speed_only.eq(self.low_speed_only & ~self.always_fs), reset_sequencer.full_speed_only.eq(self.full_speed_only | self.always_fs), self.utmi.op_mode.eq(reset_sequencer.operating_mode), self.utmi.xcvr_select.eq(reset_sequencer.current_speed), self.utmi.term_select.eq(reset_sequencer.termination_select & self.connect), ] # # Frame/microframe state. # # Handle each new SOF token as we receive them. with m.If(token_detector.interface.new_frame): # Update our knowledge of the current frame number. m.d.usb += self.frame_number.eq(token_detector.interface.frame) # Check if we're receiving a new 1ms frame -- which occurs when the new SOF's # frame number is different from the previous one's. This will always be the case # on full speed links; and will be the case 1/8th of the time on High Speed links. m.d.comb += self.new_frame.eq( token_detector.interface.frame != self.frame_number) # If this is a new frame, our microframe count should be zero. with m.If(self.new_frame): m.d.usb += self.microframe_number.eq(0) # Otherwise, this SOF indicates a new _microframe_ [USB 2.0: 8.4.3.1]. with m.Else(): m.d.usb += self.microframe_number.eq(self.microframe_number + 1) # # Device-state outputs. # m.d.comb += [ self.speed.eq(reset_sequencer.current_speed), self.suspended.eq(reset_sequencer.suspended), self.sof_detected.eq(token_detector.interface.new_frame), self.reset_detected.eq(reset_sequencer.bus_reset), self.tx_activity_led.eq(tx_multiplexer.output.valid), self.rx_activity_led.eq(self.utmi.rx_valid) ] return m
class TxShifter(Elaboratable): """Transmit Shifter TxShifter accepts parallel data and shifts it out serially. Parameters ---------- Parameters are passed in via the constructor. width : int Width of the data to be shifted. Input Ports ----------- Input ports are passed in via the constructor. i_data: Signal(width) Data to be transmitted. i_enable: Signal(), input When asserted, shifting will be allowed; otherwise, the shifter will be stalled. Output Ports ------------ Output ports are data members of the module. All outputs are flopped. o_data : Signal() Serial data output. o_empty : Signal() Asserted the cycle before the shifter loads in more i_data. o_get : Signal() Asserted the cycle after the shifter loads in i_data. """ def __init__(self, width): self._width = width # # I/O Port # self.i_data = Signal(width) self.i_enable = Signal() self.i_clear = Signal() self.o_get = Signal() self.o_empty = Signal() self.o_data = Signal() def elaborate(self, platform): m = Module() shifter = Signal(self._width) pos = Signal(self._width, reset=0b1) with m.If(self.i_enable): empty = Signal() m.d.usb += [ pos.eq(pos >> 1), shifter.eq(shifter >> 1), self.o_get.eq(empty), ] with m.If(empty): m.d.usb += [ shifter.eq(self.i_data), pos.eq(1 << (self._width - 1)), ] with m.If(self.i_clear): m.d.usb += [shifter.eq(0), pos.eq(1)] m.d.comb += [ empty.eq(pos[0]), self.o_empty.eq(empty), self.o_data.eq(shifter[0]), ] return m
class Polynomal(Elaboratable): """ Sets motor states using a polynomal algorithm A polynomal up to 3 order, i.e. c*t^3+b*t^2+a*t, is evaluated under the assumption that t starts at 0 and has a maximum of say 10_000 ticks. The polynomal describes the stepper position of a single axis. A counter is used to capture the state of the polynomal. If a given bit, denoted by bitshift, of the counter changes, a step is sent to the motor. In every tick the step can at most increase with one count. Non step part of base Counters are kept after segment. Higher orders, velocity etc are removed. This code requires a lot of LUT, only order 2 is supported on UP5k It is assumed that the user can completely determine the outcome of the calculation. To ascertain step accuracy, c is submitted with a very high accuracy. For third order, this requires 41 bit wide numbers and is a "weakness" in the code. The code might be sped up via Horner's method and the use of DSPs. The current code does not require a DSP. Assumptions: max ticks per move is 10_000 update frequency motor is 1 MHz I/O signals: I: coeff -- polynomal coefficients I: start -- start signal O: busy -- busy signal O: finished -- finished signal O: total steps -- total steps executed in move O: dir -- direction; 1 is postive and 0 is negative O: step -- step signal """ def __init__(self, platform=None, top=False): ''' platform -- pass test platform top -- trigger synthesis of module ''' self.top = top self.platform = platform self.divider = platform.clks[platform.hfosc_div] self.order = platform.poldegree self.bit_shift = bit_shift(platform) self.motors = platform.motors self.max_steps = int(MOVE_TICKS / 2) # Nyquist # inputs self.coeff = Array() for _ in range(self.motors): self.coeff.extend([ Signal(signed(self.bit_shift + 1)), Signal(signed(self.bit_shift + 1)), Signal(signed(self.bit_shift + 1)) ][:self.order]) self.start = Signal() self.ticklimit = Signal(MOVE_TICKS.bit_length()) # output self.busy = Signal() self.dir = Array(Signal() for _ in range(self.motors)) self.step = Array(Signal() for _ in range(self.motors)) def elaborate(self, platform): m = Module() # add 1 MHz clock domain cntr = Signal(range(self.divider)) # pos max_bits = (self.max_steps << self.bit_shift).bit_length() cntrs = Array( Signal(signed(max_bits + 1)) for _ in range(len(self.coeff))) assert max_bits <= 64 ticks = Signal(MOVE_TICKS.bit_length()) if self.top: steppers = [res for res in get_all_resources(platform, "stepper")] assert len(steppers) != 0 for idx, stepper in enumerate(steppers): m.d.comb += [ stepper.step.eq(self.step[idx]), stepper.dir.eq(self.dir[idx]) ] else: self.ticks = ticks self.cntrs = cntrs # steps for motor in range(self.motors): m.d.comb += self.step[motor].eq(cntrs[motor * self.order][self.bit_shift]) # directions counter_d = Array( Signal(signed(max_bits + 1)) for _ in range(self.motors)) for motor in range(self.motors): m.d.sync += counter_d[motor].eq(cntrs[motor * self.order]) # negative case --> decreasing with m.If(counter_d[motor] > cntrs[motor * self.order]): m.d.sync += self.dir[motor].eq(0) # positive case --> increasing with m.Elif(counter_d[motor] < cntrs[motor * self.order]): m.d.sync += self.dir[motor].eq(1) with m.FSM(reset='RESET', name='polynomen'): with m.State('RESET'): m.next = 'WAIT_START' m.d.sync += self.busy.eq(0) with m.State('WAIT_START'): with m.If(self.start): for motor in range(self.motors): coef0 = motor * self.order step_bit = self.bit_shift + 1 m.d.sync += [ cntrs[coef0].eq(cntrs[coef0][:step_bit]), counter_d[motor].eq(counter_d[motor][:step_bit]) ] for degree in range(1, self.order): m.d.sync += cntrs[coef0 + degree].eq(0) m.d.sync += self.busy.eq(1) m.next = 'RUNNING' with m.Else(): m.d.sync += self.busy.eq(0) with m.State('RUNNING'): with m.If((ticks < self.ticklimit) & (cntr >= self.divider - 1)): m.d.sync += [ticks.eq(ticks + 1), cntr.eq(0)] for motor in range(self.motors): order = self.order idx = motor * order op3, op2, op1 = 0, 0, 0 if order > 2: op3 += 3 * 2 * self.coeff[idx + 2] + cntrs[idx + 2] op2 += cntrs[idx + 2] op1 += self.coeff[idx + 2] + cntrs[idx + 2] m.d.sync += cntrs[idx + 2].eq(op3) if order > 1: op2 += (2 * self.coeff[idx + 1] + cntrs[idx + 1]) m.d.sync += cntrs[idx + 1].eq(op2) op1 += (self.coeff[idx + 1] + self.coeff[idx] + cntrs[idx + 1] + cntrs[idx]) m.d.sync += cntrs[idx].eq(op1) with m.Elif(ticks < self.ticklimit): m.d.sync += cntr.eq(cntr + 1) with m.Else(): m.d.sync += ticks.eq(0) m.next = 'WAIT_START' return m
class PostProcessor(SimpleElaboratable): """Does post-processing of an accumulator value. This is a pipeline: place values at inputs and outputs appear 4 cycles later. It is capable of producing one result per cycle. The function being implemented is: acc += param_store_read(&output_bias); acc = cpp_math_mul_by_quantized_mul_software( acc, param_store_read(&output_multiplier), param_store_read(&output_shift)); acc += reg_output_offset; if (acc < reg_activation_min) { acc = reg_activation_min; } else if (acc > reg_activation_max) { acc = reg_activation_max; } return acc; Attributes --------- accumulator: Signal(signed(32)) input The accumulator value to be post processed bias: Signal(signed(32)) input Bias to add to accumulator multiplier: Signal(signed(32)) input output multiplier to apply shift: Signal(signed(32)) input shift to apply (negative for right shift) offset: Signal(signed(32)) input amount to transform output by before clamping activation_min: Signal(signed(32)) input minimum clamp for output activation_max: Signal(signed(32)) input maximum clamp for output result: Signal(signed(32)) output The post processed result """ # TODO: see if we can make this 3 cycles by bringing SRDHM down to 2 cycles PIPELINE_CYCLES = 4 def __init__(self): self.accumulator = Signal(signed(32)) self.bias = Signal(signed(32)) self.multiplier = Signal(signed(32)) self.shift = Signal(signed(32)) self.offset = Signal(signed(32)) self.activation_min = Signal(signed(32)) self.activation_max = Signal(signed(32)) self.result = Signal(signed(32)) def elab(self, m): with_bias = Signal(signed(32)) m.d.comb += with_bias.eq(self.accumulator + self.bias) # acc = cpp_math_mul_by_quantized_mul_software( # acc, param_store_read(&output_multiplier), # param_store_read(&output_shift)); left_shift = Signal(5) right_sr = [Signal(5, name=f'right_sr_{n}') for n in range(4)] with m.If(self.shift > 0): m.d.comb += left_shift.eq(self.shift) with m.Else(): m.d.comb += right_sr[0].eq(-self.shift) left_shifted = Signal(32) m.d.comb += left_shifted.eq(with_bias << left_shift), # Pass right shift value down through several cycles to where # it is needed for a, b in zip(right_sr, right_sr[1:]): m.d.sync += b.eq(a) # All logic is combinational up to the inputs to the SRDHM m.submodules['srdhm'] = srdhm = SRDHM() m.d.comb += [ srdhm.a.eq(left_shifted), srdhm.b.eq(self.multiplier), ] # Output from SRDHM appears several cycles later right_shifted = Signal(signed(32)) m.d.sync += right_shifted.eq( rounding_divide_by_pot(srdhm.result, right_sr[-1])) # This logic is combinational to output # acc += reg_output_offset # if (acc < reg_activation_min) { # acc = reg_activation_min # } else if (acc > reg_activation_max) { # acc = reg_activation_max # } # return acc with_offset = Signal(signed(32)) m.d.comb += [ with_offset.eq(right_shifted + self.offset), self.result.eq( clamped(with_offset, self.activation_min, self.activation_max)), ]
def elaborate(self, platform): m = Module() # Parser parser = SPIParser(self.platform) m.submodules.parser = parser # Busy used to detect move or scanline in action # disabled "dispatching" busy = Signal() # Polynomal Move polynomal = Polynomal(self.platform) m.submodules.polynomal = polynomal if platform: board_spi = platform.request("debug_spi") spi = synchronize(m, board_spi) laserheadpins = platform.request("laserscanner") steppers = [res for res in get_all_resources(platform, "stepper")] bldc = platform.request("bldc") leds = [res.o for res in get_all_resources(platform, "led")] assert len(steppers) != 0 else: platform = self.platform self.spi = SPIBus() self.parser = parser self.pol = polynomal spi = synchronize(m, self.spi) self.laserheadpins = platform.laserhead self.steppers = steppers = platform.steppers self.busy = busy laserheadpins = platform.laserhead bldc = platform.bldc leds = platform.leds # Local laser signal clones enable_prism = Signal() lasers = Signal(2) # Laserscan Head if self.simdiode: laserhead = DiodeSimulator(platform=platform, addfifo=False) lh = laserhead m.d.comb += [ lh.enable_prism_in.eq(enable_prism | lh.enable_prism), lh.laser0in.eq(lasers[0] | lh.lasers[0]), laserhead.laser1in.eq(lasers[1] | lh.lasers[1]) ] else: laserhead = Laserhead(platform=platform) m.d.comb += laserhead.photodiode.eq(laserheadpins.photodiode) m.submodules.laserhead = laserhead if platform.name == 'Test': self.laserhead = laserhead # polynomal iterates over count coeffcnt = Signal(range(len(polynomal.coeff) + 1)) # Prism motor prism_driver = Driver(platform) m.submodules.prism_driver = prism_driver # connect prism motor for idx in range(len(leds)): m.d.comb += leds[idx].eq(prism_driver.leds[idx]) m.d.comb += prism_driver.enable_prism.eq(enable_prism) m.d.comb += [ bldc.uL.eq(prism_driver.uL), bldc.uH.eq(prism_driver.uH), bldc.vL.eq(prism_driver.vL), bldc.vH.eq(prism_driver.vH), bldc.wL.eq(prism_driver.wL), bldc.wH.eq(prism_driver.wH) ] m.d.comb += [ prism_driver.hall[0].eq(bldc.sensor0), prism_driver.hall[1].eq(bldc.sensor1), prism_driver.hall[2].eq(bldc.sensor2) ] # connect laserhead m.d.comb += [ # TODO: fix removal # laserheadpins.pwm.eq(laserhead.pwm), # laserheadpins.en.eq(laserhead.enable_prism | enable_prism), laserheadpins.laser0.eq(laserhead.lasers[0] | lasers[0]), laserheadpins.laser1.eq(laserhead.lasers[1] | lasers[1]), ] # connect Parser m.d.comb += [ self.read_data.eq(parser.read_data), laserhead.read_data.eq(parser.read_data), laserhead.empty.eq(parser.empty), self.empty.eq(parser.empty), parser.read_commit.eq(self.read_commit | laserhead.read_commit), parser.read_en.eq(self.read_en | laserhead.read_en), parser.read_discard.eq(self.read_discard | laserhead.read_discard) ] # connect motors for idx, stepper in enumerate(steppers): step = (polynomal.step[idx] & ((stepper.limit == 0) | stepper.dir)) if idx != (list(platform.stepspermm.keys()).index( platform.laser_axis)): direction = polynomal.dir[idx] m.d.comb += [ stepper.step.eq(step), stepper.dir.eq(direction), parser.pinstate[idx].eq(stepper.limit) ] # connect the motor in which the laserhead moves to laser core else: m.d.comb += [ parser.pinstate[idx].eq(stepper.limit), stepper.step.eq((step & (~laserhead.process_lines)) | (laserhead.step & (laserhead.process_lines))), stepper.dir.eq( (polynomal.dir[idx] & (~laserhead.process_lines)) | (laserhead.dir & (laserhead.process_lines))) ] m.d.comb += (parser.pinstate[len(steppers):].eq( Cat(laserhead.photodiode_t, laserhead.synchronized))) # update position stepper_d = Array(Signal() for _ in range(len(steppers))) for idx, stepper in enumerate(steppers): pos = parser.position[idx] m.d.sync += stepper_d[idx].eq(stepper.step) with m.If(stepper.limit == 1): m.d.sync += parser.position[idx].eq(0) # assuming position is signed # TODO: this might eat LUT, optimize pos_max = pow(2, pos.width - 1) - 2 with m.Elif((pos > pos_max) | (pos < -pos_max)): m.d.sync += parser.position[idx].eq(0) with m.Elif((stepper.step == 1) & (stepper_d[idx] == 0)): with m.If(stepper.dir): m.d.sync += pos.eq(pos + 1) with m.Else(): m.d.sync += pos.eq(pos - 1) # Busy signal m.d.comb += busy.eq(polynomal.busy | laserhead.process_lines) # connect spi m.d.comb += parser.spi.connect(spi) # pins you can write to pins = Cat(lasers, enable_prism, laserhead.synchronize) with m.FSM(reset='RESET', name='dispatcher'): with m.State('RESET'): m.next = 'WAIT_INSTRUCTION' m.d.sync += pins.eq(0) with m.State('WAIT_INSTRUCTION'): m.d.sync += [self.read_commit.eq(0), polynomal.start.eq(0)] with m.If((self.empty == 0) & parser.parse & (busy == 0)): m.d.sync += self.read_en.eq(1) m.next = 'PARSEHEAD' # check which instruction we r handling with m.State('PARSEHEAD'): byte0 = self.read_data[:8] m.d.sync += self.read_en.eq(0) with m.If(byte0 == INSTRUCTIONS.MOVE): m.d.sync += [ polynomal.ticklimit.eq(self.read_data[8:]), coeffcnt.eq(0) ] m.next = 'MOVE_POLYNOMAL' with m.Elif(byte0 == INSTRUCTIONS.WRITEPIN): m.d.sync += [ pins.eq(self.read_data[8:]), self.read_commit.eq(1) ] m.next = 'WAIT' with m.Elif((byte0 == INSTRUCTIONS.SCANLINE) | (byte0 == INSTRUCTIONS.LASTSCANLINE)): m.d.sync += [ self.read_discard.eq(1), laserhead.synchronize.eq(1), laserhead.expose_start.eq(1) ] m.next = 'SCANLINE' with m.Else(): m.next = 'ERROR' m.d.sync += parser.dispatcherror.eq(1) with m.State('MOVE_POLYNOMAL'): with m.If(coeffcnt < len(polynomal.coeff)): with m.If(self.read_en == 0): m.d.sync += self.read_en.eq(1) with m.Else(): m.d.sync += [ polynomal.coeff[coeffcnt].eq(self.read_data), coeffcnt.eq(coeffcnt + 1), self.read_en.eq(0) ] with m.Else(): m.next = 'WAIT' m.d.sync += [polynomal.start.eq(1), self.read_commit.eq(1)] with m.State('SCANLINE'): m.d.sync += [ self.read_discard.eq(0), laserhead.expose_start.eq(0) ] m.next = 'WAIT' # NOTE: you need to wait for busy to be raised # in time with m.State('WAIT'): m.d.sync += polynomal.start.eq(0) m.next = 'WAIT_INSTRUCTION' # NOTE: system never recovers user must reset with m.State('ERROR'): m.next = 'ERROR' return m
def __init__(self): super().__init__() self.bias = Signal(signed(32)) self.bias_next = Signal() self.multiplier = Signal(signed(32)) self.multiplier_next = Signal() self.shift = Signal(signed(32)) self.shift_next = Signal() self.offset = Signal(signed(32)) self.activation_min = Signal(signed(32)) self.activation_max = Signal(signed(32))
def __init__(self): self.a = Signal(signed(32)) self.b = Signal(signed(32)) self.result = Signal(signed(32))
class PostProcessXetter(Xetter): """Does post-processing of an accumulator value. The output channel index is implied by processing order. This is mostly a wrapper around PostProcessor. Attributes --------- bias: Signal(signed(32)) input output_bias from param store bias_next: Signal() output signal that output_bias has been read multiplier: Signal(signed(32)) input output_multiplier from param store multiplier_next: Signal() output signal that output_multiplier has been read shift: Signal(signed(32)) input output_shift from param store shift_next: Signal() output signal that output_shift has been read offset: Signal(signed(32)) input amount to transform output by before clamping activation_min: Signal(signed(32)) input minimum clamp for output activation_max: Signal(signed(32)) input maximum clamp for output """ def __init__(self): super().__init__() self.bias = Signal(signed(32)) self.bias_next = Signal() self.multiplier = Signal(signed(32)) self.multiplier_next = Signal() self.shift = Signal(signed(32)) self.shift_next = Signal() self.offset = Signal(signed(32)) self.activation_min = Signal(signed(32)) self.activation_max = Signal(signed(32)) def elab(self, m): m.submodules['pp'] = pp = PostProcessor() # Connections to post processor m.d.comb += [ pp.accumulator.eq(self.in0.as_signed()), pp.bias.eq(self.bias), pp.multiplier.eq(self.multiplier), pp.shift.eq(self.shift), pp.offset.eq(self.offset), pp.activation_min.eq(self.activation_min), pp.activation_max.eq(self.activation_max), self.output.eq(pp.result), ] # Use a sequencer to count down to processing end m.submodules['delay'] = delay = Delayer(PostProcessor.PIPELINE_CYCLES) m.d.comb += delay.input.eq(self.start) # Other control signal outputs - set *_next to indicate values used # Set done to fire when calculation is complete m.d.comb += [ self.bias_next.eq(self.start), self.multiplier_next.eq(self.start), self.shift_next.eq(self.start), self.done.eq(delay.output), ]
def elab(self, m): with_bias = Signal(signed(32)) m.d.comb += with_bias.eq(self.accumulator + self.bias) # acc = cpp_math_mul_by_quantized_mul_software( # acc, param_store_read(&output_multiplier), # param_store_read(&output_shift)); left_shift = Signal(5) right_sr = [Signal(5, name=f'right_sr_{n}') for n in range(4)] with m.If(self.shift > 0): m.d.comb += left_shift.eq(self.shift) with m.Else(): m.d.comb += right_sr[0].eq(-self.shift) left_shifted = Signal(32) m.d.comb += left_shifted.eq(with_bias << left_shift), # Pass right shift value down through several cycles to where # it is needed for a, b in zip(right_sr, right_sr[1:]): m.d.sync += b.eq(a) # All logic is combinational up to the inputs to the SRDHM m.submodules['srdhm'] = srdhm = SRDHM() m.d.comb += [ srdhm.a.eq(left_shifted), srdhm.b.eq(self.multiplier), ] # Output from SRDHM appears several cycles later right_shifted = Signal(signed(32)) m.d.sync += right_shifted.eq( rounding_divide_by_pot(srdhm.result, right_sr[-1])) # This logic is combinational to output # acc += reg_output_offset # if (acc < reg_activation_min) { # acc = reg_activation_min # } else if (acc > reg_activation_max) { # acc = reg_activation_max # } # return acc with_offset = Signal(signed(32)) m.d.comb += [ with_offset.eq(right_shifted + self.offset), self.result.eq( clamped(with_offset, self.activation_min, self.activation_max)), ]
class SetInstruction(InstructionBase): """Handles sending values from CPU to CFU Attributes ---------- reg_verify_value: Signal(32), out The value last set into the verify register accelerator_start: Signal(), out Toggles when start register written accelerator_reset: Signal(), out Toggles when reset register written config: Record(ACCELERATOR_CONFIGURATION_LAYOUT), out Configuration values for accelerator core, as received from set instructions. filter_output: Endpoint(FILTER_WRITE_COMMAND), out Write command for filter store post_process_params: Endpoint(POST_PROCESS_PARAMS), out Stream of data to write to post_process memory. """ def __init__(self): super().__init__() self.reg_verify_value = Signal(32) self.accelerator_start = Signal() self.accelerator_reset = Signal() self.config = Record(ACCELERATOR_CONFIGURATION_LAYOUT) self.filter_output = Endpoint(FILTER_WRITE_COMMAND) self.post_process_params = Endpoint(POST_PROCESS_PARAMS) def elab(self, m): # Default toggles to off m.d.sync += self.accelerator_start.eq(0) m.d.sync += self.accelerator_reset.eq(0) m.d.sync += self.filter_output.valid.eq(0) m.d.sync += self.post_process_params.valid.eq(0) # All sets take exactly one cycle m.d.sync += self.done.eq(0) # Perform action with m.If(self.start): with m.Switch(self.funct7): with m.Case(Constants.REG_VERIFY): m.d.sync += self.reg_verify_value.eq(self.in0) with m.Case(Constants.REG_ACCELERATOR_START): m.d.sync += self.accelerator_start.eq(1) with m.Case(Constants.REG_ACCELERATOR_RESET): m.d.sync += self.accelerator_reset.eq(1) with m.Case(Constants.REG_FILTER_WRITE): m.d.sync += [ self.filter_output.payload.store.eq(self.in0[16:]), self.filter_output.payload.addr.eq(self.in0[:16]), self.filter_output.payload.data.eq(self.in1), self.filter_output.valid.eq(1), ] with m.Case(Constants.REG_MODE): m.d.sync += self.config.mode.eq(self.in0) with m.Case(Constants.REG_INPUT_OFFSET): m.d.sync += self.config.input_offset.eq(self.in0s) with m.Case(Constants.REG_NUM_FILTER_WORDS): m.d.sync += self.config.num_filter_words.eq(self.in0) with m.Case(Constants.REG_OUTPUT_OFFSET): m.d.sync += self.config.output_offset.eq(self.in0s) with m.Case(Constants.REG_OUTPUT_ACTIVATION_MIN): m.d.sync += self.config.output_activation_min.eq(self.in0s) with m.Case(Constants.REG_OUTPUT_ACTIVATION_MAX): m.d.sync += self.config.output_activation_max.eq(self.in0s) with m.Case(Constants.REG_INPUT_BASE_ADDR): m.d.sync += self.config.input_base_addr.eq(self.in0) with m.Case(Constants.REG_NUM_PIXELS_X): m.d.sync += self.config.num_pixels_x.eq(self.in0) with m.Case(Constants.REG_PIXEL_ADVANCE_X): m.d.sync += self.config.pixel_advance_x.eq(self.in0) with m.Case(Constants.REG_PIXEL_ADVANCE_Y): m.d.sync += self.config.pixel_advance_y.eq(self.in0) with m.Case(Constants.REG_INPUT_CHANNEL_DEPTH): m.d.sync += self.config.input_channel_depth.eq(self.in0) with m.Case(Constants.REG_OUTPUT_CHANNEL_DEPTH): m.d.sync += self.config.output_channel_depth.eq(self.in0) with m.Case(Constants.REG_NUM_OUTPUT_VALUES): m.d.sync += self.config.num_output_values.eq(self.in0) with m.Case(Constants.REG_POST_PROCESS_BIAS): m.d.sync += self.post_process_params.payload.bias.eq( self.in0s) with m.Case(Constants.REG_POST_PROCESS_SHIFT): m.d.sync += self.post_process_params.payload.shift.eq( self.in0) with m.Case(Constants.REG_POST_PROCESS_MULTIPLIER): m.d.sync += [ self.post_process_params.payload.multiplier.eq( self.in0s), self.post_process_params.valid.eq(1), ] m.d.sync += self.done.eq(1)
def __init__(self): self.i_bit_strobe = Signal() self.i_data_payload = Signal(8) self.o_data_strobe = Signal() self.i_oe = Signal() self.o_usbp = Signal() self.o_usbn = Signal() self.o_oe = Signal() self.o_pkt_end = Signal() self.fit_dat = Signal() self.fit_oe = Signal()
class SPIParser(Elaboratable): """ Parses and replies to commands over SPI The following commmands are possible status -- send back state of the peripheriral start -- enables parsing of FIFO stop -- halts parsing of FIFO write -- write instruction to FIFO or report memory is full I/O signals: I/O: Spibus -- spi bus connected to peripheral I: positions -- positions of stepper motors I: pin state -- used to get the value of select pins at client I: read_commit -- finalize read transactionalizedfifo I: read_en -- enable read transactionalizedfifo I: read_discard -- read discard of transactionalizedfifo I: dispatcherror -- error while processing stored command from spi O: execute -- start processing gcode O: read_data -- read data from transactionalizedfifo O: empty -- transactionalizedfifo is empty """ def __init__(self, platform, top=False): """ platform -- pass test platform top -- trigger synthesis of module """ self.platform = platform self.top = top self.spi = SPIBus() self.position = Array( Signal(signed(64)) for _ in range(platform.motors)) self.pinstate = Signal(8) self.read_commit = Signal() self.read_en = Signal() self.read_discard = Signal() self.dispatcherror = Signal() self.parse = Signal() self.read_data = Signal(MEMWIDTH) self.empty = Signal() def elaborate(self, platform): m = Module() if platform and self.top: board_spi = platform.request("debug_spi") spi2 = synchronize(m, board_spi) m.d.comb += self.spi.connect(spi2) if self.platform: platform = self.platform spi = self.spi interf = SPICommandInterface(command_size=COMMAND_BYTES * 8, word_size=WORD_BYTES * 8) m.d.comb += interf.spi.connect(spi) m.submodules.interf = interf # FIFO connection fifo = TransactionalizedFIFO(width=MEMWIDTH, depth=platform.memdepth) if platform.name == 'Test': self.fifo = fifo m.submodules.fifo = fifo m.d.comb += [ self.read_data.eq(fifo.read_data), fifo.read_commit.eq(self.read_commit), fifo.read_discard.eq(self.read_discard), fifo.read_en.eq(self.read_en), self.empty.eq(fifo.empty) ] # Parser mtrcntr = Signal(range(platform.motors)) wordsreceived = Signal(range(wordsinmove(platform) + 1)) error = Signal() # Peripheral state state = Signal(8) m.d.sync += [ state[STATE.PARSING].eq(self.parse), state[STATE.FULL].eq(fifo.space_available <= 1), state[STATE.ERROR].eq(self.dispatcherror | error) ] # remember which word we are processing instruction = Signal(8) with m.FSM(reset='RESET', name='parser'): with m.State('RESET'): m.d.sync += [ self.parse.eq(1), wordsreceived.eq(0), error.eq(0) ] m.next = 'WAIT_COMMAND' with m.State('WAIT_COMMAND'): with m.If(interf.command_ready): word = Cat(state[::-1], self.pinstate[::-1]) with m.If(interf.command == COMMANDS.EMPTY): m.next = 'WAIT_COMMAND' with m.Elif(interf.command == COMMANDS.START): m.next = 'WAIT_COMMAND' m.d.sync += self.parse.eq(1) with m.Elif(interf.command == COMMANDS.STOP): m.next = 'WAIT_COMMAND' m.d.sync += self.parse.eq(0) with m.Elif(interf.command == COMMANDS.WRITE): m.d.sync += interf.word_to_send.eq(word) with m.If(state[STATE.FULL] == 0): m.next = 'WAIT_WORD' with m.Else(): m.next = 'WAIT_COMMAND' with m.Elif(interf.command == COMMANDS.READ): m.d.sync += interf.word_to_send.eq(word) m.next = 'WAIT_COMMAND' with m.Elif(interf.command == COMMANDS.POSITION): # position is requested multiple times for multiple # motors with m.If(mtrcntr < platform.motors - 1): m.d.sync += mtrcntr.eq(mtrcntr + 1) with m.Else(): m.d.sync += mtrcntr.eq(0) m.d.sync += interf.word_to_send.eq( self.position[mtrcntr]) m.next = 'WAIT_COMMAND' with m.State('WAIT_WORD'): with m.If(interf.word_complete): byte0 = interf.word_received[:8] with m.If(wordsreceived == 0): with m.If((byte0 > 0) & (byte0 < 6)): m.d.sync += [ instruction.eq(byte0), fifo.write_en.eq(1), wordsreceived.eq(wordsreceived + 1), fifo.write_data.eq(interf.word_received) ] m.next = 'WRITE' with m.Else(): m.d.sync += error.eq(1) m.next = 'WAIT_COMMAND' with m.Else(): m.d.sync += [ fifo.write_en.eq(1), wordsreceived.eq(wordsreceived + 1), fifo.write_data.eq(interf.word_received) ] m.next = 'WRITE' with m.State('WRITE'): m.d.sync += fifo.write_en.eq(0) wordslaser = wordsinscanline( params(platform)['BITSINSCANLINE']) wordsmotor = wordsinmove(platform) with m.If(((instruction == INSTRUCTIONS.MOVE) & (wordsreceived >= wordsmotor)) | (instruction == INSTRUCTIONS.WRITEPIN) | (instruction == INSTRUCTIONS.LASTSCANLINE) | ((instruction == INSTRUCTIONS.SCANLINE) & (wordsreceived >= wordslaser))): m.d.sync += [wordsreceived.eq(0), fifo.write_commit.eq(1)] m.next = 'COMMIT' with m.Else(): m.next = 'WAIT_COMMAND' with m.State('COMMIT'): m.d.sync += fifo.write_commit.eq(0) m.next = 'WAIT_COMMAND' return m
def elaborate(self, platform): m = Module() sync_pulse = Signal(8) da_reset_shifter = Signal() da_reset_bitstuff = Signal( ) # Need to reset the bit stuffer 1 cycle after the shifter. stall = Signal() # These signals are set during the sync pulse sp_reset_bitstuff = Signal() sp_reset_shifter = Signal() sp_bit = Signal() sp_o_data_strobe = Signal() # 12MHz domain bitstuff_valid_data = Signal() # Keep a Gray counter around to smoothly transition between states state_gray = Signal(2) state_data = Signal() state_sync = Signal() # # Transmit gearing. # m.submodules.shifter = shifter = TxShifter(width=8) m.d.comb += [ shifter.i_data.eq(self.i_data_payload), shifter.i_enable.eq(~stall), shifter.i_clear.eq(da_reset_shifter | sp_reset_shifter) ] # # Bit-stuffing and NRZI. # bitstuff = ResetInserter(da_reset_bitstuff)(TxBitstuffer()) m.submodules.bitstuff = bitstuff m.submodules.nrzi = nrzi = TxNRZIEncoder() # # Transmit controller. # m.d.comb += [ # Send a data strobe when we're two bits from the end of the sync pulse. # This is because the pipeline takes two bit times, and we want to ensure the pipeline # has spooled up enough by the time we're there. bitstuff.i_data.eq(shifter.o_data), stall.eq(bitstuff.o_stall), sp_bit.eq(sync_pulse[0]), sp_reset_bitstuff.eq(sync_pulse[0]), # The shifter has one clock cycle of latency, so reset it # one cycle before the end of the sync byte. sp_reset_shifter.eq(sync_pulse[1]), sp_o_data_strobe.eq(sync_pulse[5]), state_data.eq(state_gray[0] & state_gray[1]), state_sync.eq(state_gray[0] & ~state_gray[1]), self.fit_oe.eq(state_data | state_sync), self.fit_dat.eq((state_data & shifter.o_data & ~bitstuff.o_stall) | sp_bit), self.o_data_strobe.eq(state_data & shifter.o_get & ~stall & self.i_oe), ] # If we reset the shifter, then o_empty will go high on the next cycle. # m.d.usb += [ # If the shifter runs out of data, percolate the "reset" signal to the # shifter, and then down to the bitstuffer. # da_reset_shifter.eq(~stall & shifter.o_empty & ~da_stalled_reset), # da_stalled_reset.eq(da_reset_shifter), # da_reset_bitstuff.eq(~stall & da_reset_shifter), bitstuff_valid_data.eq(~stall & shifter.o_get & self.i_oe), ] with m.FSM(domain="usb"): with m.State('IDLE'): with m.If(self.i_oe): m.d.usb += [sync_pulse.eq(1 << 7), state_gray.eq(0b01)] m.next = "SEND_SYNC" with m.Else(): m.d.usb += state_gray.eq(0b00) with m.State('SEND_SYNC'): m.d.usb += sync_pulse.eq(sync_pulse >> 1) with m.If(sync_pulse[0]): m.d.usb += state_gray.eq(0b11) m.next = "SEND_DATA" with m.Else(): m.d.usb += state_gray.eq(0b01) with m.State('SEND_DATA'): with m.If(~self.i_oe & shifter.o_empty & ~bitstuff.o_stall): with m.If(bitstuff.o_will_stall): m.next = 'STUFF_LAST_BIT' with m.Else(): m.d.usb += state_gray.eq(0b10) m.next = 'IDLE' with m.Else(): m.d.usb += state_gray.eq(0b11) with m.State('STUFF_LAST_BIT'): m.d.usb += state_gray.eq(0b10) m.next = 'IDLE' # 48MHz domain # NRZI encoding nrzi_dat = Signal() nrzi_oe = Signal() # Cross the data from the 12MHz domain to the 48MHz domain cdc_dat = FFSynchronizer(self.fit_dat, nrzi_dat, o_domain="usb_io", stages=3) cdc_oe = FFSynchronizer(self.fit_oe, nrzi_oe, o_domain="usb_io", stages=3) m.submodules += [cdc_dat, cdc_oe] m.d.comb += [ nrzi.i_valid.eq(self.i_bit_strobe), nrzi.i_data.eq(nrzi_dat), nrzi.i_oe.eq(nrzi_oe), self.o_usbp.eq(nrzi.o_usbp), self.o_usbn.eq(nrzi.o_usbn), self.o_oe.eq(nrzi.o_oe), ] return m
def __init__(self, platform, top=False): """ platform -- pass test platform top -- trigger synthesis of module """ self.platform = platform self.top = top self.spi = SPIBus() self.position = Array( Signal(signed(64)) for _ in range(platform.motors)) self.pinstate = Signal(8) self.read_commit = Signal() self.read_en = Signal() self.read_discard = Signal() self.dispatcherror = Signal() self.parse = Signal() self.read_data = Signal(MEMWIDTH) self.empty = Signal()
def elaborate(self, platform): m = Module() shifter = Signal(self._width) pos = Signal(self._width, reset=0b1) with m.If(self.i_enable): empty = Signal() m.d.usb += [ pos.eq(pos >> 1), shifter.eq(shifter >> 1), self.o_get.eq(empty), ] with m.If(empty): m.d.usb += [ shifter.eq(self.i_data), pos.eq(1 << (self._width - 1)), ] with m.If(self.i_clear): m.d.usb += [shifter.eq(0), pos.eq(1)] m.d.comb += [ empty.eq(pos[0]), self.o_empty.eq(empty), self.o_data.eq(shifter[0]), ] return m
def elaborate(self, platform): m = Module() if platform and self.top: board_spi = platform.request("debug_spi") spi2 = synchronize(m, board_spi) m.d.comb += self.spi.connect(spi2) if self.platform: platform = self.platform spi = self.spi interf = SPICommandInterface(command_size=COMMAND_BYTES * 8, word_size=WORD_BYTES * 8) m.d.comb += interf.spi.connect(spi) m.submodules.interf = interf # FIFO connection fifo = TransactionalizedFIFO(width=MEMWIDTH, depth=platform.memdepth) if platform.name == 'Test': self.fifo = fifo m.submodules.fifo = fifo m.d.comb += [ self.read_data.eq(fifo.read_data), fifo.read_commit.eq(self.read_commit), fifo.read_discard.eq(self.read_discard), fifo.read_en.eq(self.read_en), self.empty.eq(fifo.empty) ] # Parser mtrcntr = Signal(range(platform.motors)) wordsreceived = Signal(range(wordsinmove(platform) + 1)) error = Signal() # Peripheral state state = Signal(8) m.d.sync += [ state[STATE.PARSING].eq(self.parse), state[STATE.FULL].eq(fifo.space_available <= 1), state[STATE.ERROR].eq(self.dispatcherror | error) ] # remember which word we are processing instruction = Signal(8) with m.FSM(reset='RESET', name='parser'): with m.State('RESET'): m.d.sync += [ self.parse.eq(1), wordsreceived.eq(0), error.eq(0) ] m.next = 'WAIT_COMMAND' with m.State('WAIT_COMMAND'): with m.If(interf.command_ready): word = Cat(state[::-1], self.pinstate[::-1]) with m.If(interf.command == COMMANDS.EMPTY): m.next = 'WAIT_COMMAND' with m.Elif(interf.command == COMMANDS.START): m.next = 'WAIT_COMMAND' m.d.sync += self.parse.eq(1) with m.Elif(interf.command == COMMANDS.STOP): m.next = 'WAIT_COMMAND' m.d.sync += self.parse.eq(0) with m.Elif(interf.command == COMMANDS.WRITE): m.d.sync += interf.word_to_send.eq(word) with m.If(state[STATE.FULL] == 0): m.next = 'WAIT_WORD' with m.Else(): m.next = 'WAIT_COMMAND' with m.Elif(interf.command == COMMANDS.READ): m.d.sync += interf.word_to_send.eq(word) m.next = 'WAIT_COMMAND' with m.Elif(interf.command == COMMANDS.POSITION): # position is requested multiple times for multiple # motors with m.If(mtrcntr < platform.motors - 1): m.d.sync += mtrcntr.eq(mtrcntr + 1) with m.Else(): m.d.sync += mtrcntr.eq(0) m.d.sync += interf.word_to_send.eq( self.position[mtrcntr]) m.next = 'WAIT_COMMAND' with m.State('WAIT_WORD'): with m.If(interf.word_complete): byte0 = interf.word_received[:8] with m.If(wordsreceived == 0): with m.If((byte0 > 0) & (byte0 < 6)): m.d.sync += [ instruction.eq(byte0), fifo.write_en.eq(1), wordsreceived.eq(wordsreceived + 1), fifo.write_data.eq(interf.word_received) ] m.next = 'WRITE' with m.Else(): m.d.sync += error.eq(1) m.next = 'WAIT_COMMAND' with m.Else(): m.d.sync += [ fifo.write_en.eq(1), wordsreceived.eq(wordsreceived + 1), fifo.write_data.eq(interf.word_received) ] m.next = 'WRITE' with m.State('WRITE'): m.d.sync += fifo.write_en.eq(0) wordslaser = wordsinscanline( params(platform)['BITSINSCANLINE']) wordsmotor = wordsinmove(platform) with m.If(((instruction == INSTRUCTIONS.MOVE) & (wordsreceived >= wordsmotor)) | (instruction == INSTRUCTIONS.WRITEPIN) | (instruction == INSTRUCTIONS.LASTSCANLINE) | ((instruction == INSTRUCTIONS.SCANLINE) & (wordsreceived >= wordslaser))): m.d.sync += [wordsreceived.eq(0), fifo.write_commit.eq(1)] m.next = 'COMMIT' with m.Else(): m.next = 'WAIT_COMMAND' with m.State('COMMIT'): m.d.sync += fifo.write_commit.eq(0) m.next = 'WAIT_COMMAND' return m
def elaborate(self, platform): m = Module() # add 1 MHz clock domain cntr = Signal(range(self.divider)) # pos max_bits = (self.max_steps << self.bit_shift).bit_length() cntrs = Array( Signal(signed(max_bits + 1)) for _ in range(len(self.coeff))) assert max_bits <= 64 ticks = Signal(MOVE_TICKS.bit_length()) if self.top: steppers = [res for res in get_all_resources(platform, "stepper")] assert len(steppers) != 0 for idx, stepper in enumerate(steppers): m.d.comb += [ stepper.step.eq(self.step[idx]), stepper.dir.eq(self.dir[idx]) ] else: self.ticks = ticks self.cntrs = cntrs # steps for motor in range(self.motors): m.d.comb += self.step[motor].eq(cntrs[motor * self.order][self.bit_shift]) # directions counter_d = Array( Signal(signed(max_bits + 1)) for _ in range(self.motors)) for motor in range(self.motors): m.d.sync += counter_d[motor].eq(cntrs[motor * self.order]) # negative case --> decreasing with m.If(counter_d[motor] > cntrs[motor * self.order]): m.d.sync += self.dir[motor].eq(0) # positive case --> increasing with m.Elif(counter_d[motor] < cntrs[motor * self.order]): m.d.sync += self.dir[motor].eq(1) with m.FSM(reset='RESET', name='polynomen'): with m.State('RESET'): m.next = 'WAIT_START' m.d.sync += self.busy.eq(0) with m.State('WAIT_START'): with m.If(self.start): for motor in range(self.motors): coef0 = motor * self.order step_bit = self.bit_shift + 1 m.d.sync += [ cntrs[coef0].eq(cntrs[coef0][:step_bit]), counter_d[motor].eq(counter_d[motor][:step_bit]) ] for degree in range(1, self.order): m.d.sync += cntrs[coef0 + degree].eq(0) m.d.sync += self.busy.eq(1) m.next = 'RUNNING' with m.Else(): m.d.sync += self.busy.eq(0) with m.State('RUNNING'): with m.If((ticks < self.ticklimit) & (cntr >= self.divider - 1)): m.d.sync += [ticks.eq(ticks + 1), cntr.eq(0)] for motor in range(self.motors): order = self.order idx = motor * order op3, op2, op1 = 0, 0, 0 if order > 2: op3 += 3 * 2 * self.coeff[idx + 2] + cntrs[idx + 2] op2 += cntrs[idx + 2] op1 += self.coeff[idx + 2] + cntrs[idx + 2] m.d.sync += cntrs[idx + 2].eq(op3) if order > 1: op2 += (2 * self.coeff[idx + 1] + cntrs[idx + 1]) m.d.sync += cntrs[idx + 1].eq(op2) op1 += (self.coeff[idx + 1] + self.coeff[idx] + cntrs[idx + 1] + cntrs[idx]) m.d.sync += cntrs[idx].eq(op1) with m.Elif(ticks < self.ticklimit): m.d.sync += cntr.eq(cntr + 1) with m.Else(): m.d.sync += ticks.eq(0) m.next = 'WAIT_START' return m
class TxNRZIEncoder(Elaboratable): """ NRZI Encode In order to ensure there are enough bit transitions for a receiver to recover the clock usb uses NRZI encoding. This module processes the incoming dj, dk, se0, and valid signals and decodes them to data values. It also pipelines the se0 signal and passes it through unmodified. https://www.pjrc.com/teensy/beta/usb20.pdf, USB2 Spec, 7.1.8 https://en.wikipedia.org/wiki/Non-return-to-zero Clock Domain ------------ usb_48 : 48MHz Input Ports ----------- i_valid : Signal() Qualifies oe, data, and se0. i_oe : Signal() Indicates that the transmit pipeline should be driving USB. i_data : Signal() Data bit to be transmitted on USB. Qualified by o_valid. i_se0 : Signal() Overrides value of o_data when asserted and indicates that SE0 state should be asserted on USB. Qualified by o_valid. Output Ports ------------ o_usbp : Signal() Raw value of USB+ line. o_usbn : Signal() Raw value of USB- line. o_oe : Signal() When asserted it indicates that the tx pipeline should be driving USB. """ def __init__(self): self.i_valid = Signal() self.i_oe = Signal() self.i_data = Signal() # flop all outputs self.o_usbp = Signal() self.o_usbn = Signal() self.o_oe = Signal() def elaborate(self, platform): m = Module() usbp = Signal() usbn = Signal() oe = Signal() # wait for new packet to start with m.FSM(domain="usb_io"): with m.State("IDLE"): m.d.comb += [ usbp.eq(1), usbn.eq(0), oe.eq(0), ] with m.If(self.i_valid & self.i_oe): # first bit of sync always forces a transition, we idle # in J so the first output bit is K. m.next = "DK" # the output line is in state J with m.State("DJ"): m.d.comb += [ usbp.eq(1), usbn.eq(0), oe.eq(1), ] with m.If(self.i_valid): with m.If(~self.i_oe): m.next = "SE0A" with m.Elif(self.i_data): m.next = "DJ" with m.Else(): m.next = "DK" # the output line is in state K with m.State("DK"): m.d.comb += [ usbp.eq(0), usbn.eq(1), oe.eq(1), ] with m.If(self.i_valid): with m.If(~self.i_oe): m.next = "SE0A" with m.Elif(self.i_data): m.next = "DK" with m.Else(): m.next = "DJ" # first bit of the SE0 state with m.State("SE0A"): m.d.comb += [ usbp.eq(0), usbn.eq(0), oe.eq(1), ] with m.If(self.i_valid): m.next = "SE0B" # second bit of the SE0 state with m.State("SE0B"): m.d.comb += [ usbp.eq(0), usbn.eq(0), oe.eq(1), ] with m.If(self.i_valid): m.next = "EOPJ" # drive the bus back to J before relinquishing control with m.State("EOPJ"): m.d.comb += [ usbp.eq(1), usbn.eq(0), oe.eq(1), ] with m.If(self.i_valid): m.next = "IDLE" m.d.usb_io += [ self.o_oe.eq(oe), self.o_usbp.eq(usbp), self.o_usbn.eq(usbn), ] return m
def elaborate(self, platform): m = Module() # If we have a bus translator, include it in our submodules. if self.translator: m.submodules.translator = self.translator # # Internal device state. # # Stores the device's current address. Used to identify which packets are for us. address = Signal(7, reset=0) # Stores the device's current configuration. Defaults to unconfigured. configuration = Signal(8, reset=0) # # Internal interconnections. # # Create our reset sequencer, which will be in charge of detecting USB port resets, # detecting high-speed hosts, and communicating that we are a high speed device. m.submodules.reset_sequencer = reset_sequencer = USBResetSequencer() m.d.comb += [ reset_sequencer.bus_busy.eq(self.bus_busy), reset_sequencer.vbus_connected.eq(~self.utmi.session_end), reset_sequencer.line_state.eq(self.utmi.line_state), ] # Create our internal packet components: # - A token detector, which will identify and parse the tokens that start transactions. # - A data transmitter, which will transmit provided data streams. # - A data receiver, which will receive data from UTMI and convert it into streams. # - A handshake generator, which will assist in generating response packets. # - A handshake detector, which detects handshakes generated by the host. # - A data CRC16 handler, which will compute data packet CRCs. # - An interpacket delay timer, which will enforce interpacket delays. m.submodules.token_detector = token_detector = \ USBTokenDetector(utmi=self.utmi, domain_clock=self.data_clock, fs_only=self.always_fs) m.submodules.transmitter = transmitter = USBDataPacketGenerator() m.submodules.receiver = receiver = USBDataPacketReceiver( utmi=self.utmi) m.submodules.handshake_generator = handshake_generator = USBHandshakeGenerator( ) m.submodules.handshake_detector = handshake_detector = USBHandshakeDetector( utmi=self.utmi) m.submodules.data_crc = data_crc = USBDataPacketCRC() m.submodules.timer = timer = \ USBInterpacketTimer(domain_clock=self.data_clock, fs_only=self.always_fs) # Connect our transmitter/receiver to our CRC generator. data_crc.add_interface(transmitter.crc) data_crc.add_interface(receiver.data_crc) # Connect our receiver to our timer. timer.add_interface(receiver.timer) m.d.comb += [ # Ensure our token detector only responds to tokens addressed to us. token_detector.address.eq(address), # Hook up our data_crc to our receive inputs. data_crc.rx_data.eq(self.utmi.rx_data), data_crc.rx_valid.eq(self.utmi.rx_valid), # Connect our state signals to our subordinate components. token_detector.speed.eq(self.speed), timer.speed.eq(self.speed) ] # # Endpoint connections. # # Create our endpoint multiplexer... m.submodules.endpoint_mux = endpoint_mux = USBEndpointMultiplexer() endpoint_collection = endpoint_mux.shared # Connect our timer and CRC interfaces. timer.add_interface(endpoint_collection.timer) data_crc.add_interface(endpoint_collection.data_crc) m.d.comb += [ # Low-level hardware interface. token_detector.interface.connect(endpoint_collection.tokenizer), handshake_detector.detected.connect( endpoint_collection.handshakes_in), # Device state. endpoint_collection.speed.eq(self.speed), endpoint_collection.active_config.eq(configuration), endpoint_collection.active_address.eq(address), # Receive interface. receiver.stream.connect(endpoint_collection.rx), endpoint_collection.rx_complete.eq(receiver.packet_complete), endpoint_collection.rx_invalid.eq(receiver.crc_mismatch), endpoint_collection.rx_ready_for_response.eq( receiver.ready_for_response), endpoint_collection.rx_pid_toggle.eq(receiver.active_pid[3]), # Transmit interface. endpoint_collection.tx.attach(transmitter.stream), handshake_generator.issue_ack.eq( endpoint_collection.handshakes_out.ack), handshake_generator.issue_nak.eq( endpoint_collection.handshakes_out.nak), handshake_generator.issue_stall.eq( endpoint_collection.handshakes_out.stall), transmitter.data_pid.eq(endpoint_collection.tx_pid_toggle), ] # If an endpoint wants to update our address or configuration, accept the update. with m.If(endpoint_collection.address_changed): m.d.usb += address.eq(endpoint_collection.new_address) with m.If(endpoint_collection.config_changed): m.d.usb += configuration.eq(endpoint_collection.new_config) # Finally, add each of our endpoints to this module and our multiplexer. for endpoint in self._endpoints: # Create a display name for the endpoint... name = endpoint.__class__.__name__ if hasattr(m.submodules, name): name = f"{name}_{id(endpoint)}" # ... and add it, both as a submodule and to our multiplexer. endpoint_mux.add_interface(endpoint.interface) m.submodules[name] = endpoint # # Transmitter multiplexing. # # Create a multiplexer that will arbitrate access to the transmit lines. m.submodules.tx_multiplexer = tx_multiplexer = UTMIInterfaceMultiplexer( ) # Connect each of our transmitters. tx_multiplexer.add_input(reset_sequencer.tx) tx_multiplexer.add_input(transmitter.tx) tx_multiplexer.add_input(handshake_generator.tx) m.d.comb += [ # Connect our transmit multiplexer to the actual UTMI bus. tx_multiplexer.output.attach(self.utmi), # Connect up the transmit CRC interface to our UTMI bus. data_crc.tx_valid.eq(tx_multiplexer.output.valid & self.utmi.tx_ready), data_crc.tx_data.eq(tx_multiplexer.output.data), ] # # Device-state management. # # On a bus reset, clear our address and configuration. with m.If(reset_sequencer.bus_reset): m.d.usb += [ address.eq(0), configuration.eq(0), ] # Device operating state controls. m.d.comb += [ # Disable our host-mode pulldowns; as we're a device. self.utmi.dm_pulldown.eq(0), self.utmi.dp_pulldown.eq(0), # Let our reset sequencer set our USB mode and speed. reset_sequencer.low_speed_only.eq(self.low_speed_only & ~self.always_fs), reset_sequencer.full_speed_only.eq(self.full_speed_only | self.always_fs), self.utmi.op_mode.eq(reset_sequencer.operating_mode), self.utmi.xcvr_select.eq(reset_sequencer.current_speed), self.utmi.term_select.eq(reset_sequencer.termination_select & self.connect), ] # # Frame/microframe state. # # Handle each new SOF token as we receive them. with m.If(token_detector.interface.new_frame): # Update our knowledge of the current frame number. m.d.usb += self.frame_number.eq(token_detector.interface.frame) # Check if we're receiving a new 1ms frame -- which occurs when the new SOF's # frame number is different from the previous one's. This will always be the case # on full speed links; and will be the case 1/8th of the time on High Speed links. m.d.comb += self.new_frame.eq( token_detector.interface.frame != self.frame_number) # If this is a new frame, our microframe count should be zero. with m.If(self.new_frame): m.d.usb += self.microframe_number.eq(0) # Otherwise, this SOF indicates a new _microframe_ [USB 2.0: 8.4.3.1]. with m.Else(): m.d.usb += self.microframe_number.eq(self.microframe_number + 1) # # Device-state outputs. # m.d.comb += [ self.speed.eq(reset_sequencer.current_speed), self.suspended.eq(reset_sequencer.suspended), self.sof_detected.eq(token_detector.interface.new_frame), self.reset_detected.eq(reset_sequencer.bus_reset), self.tx_activity_led.eq(tx_multiplexer.output.valid), self.rx_activity_led.eq(self.utmi.rx_valid) ] return m
def elaborate(self, platform): m = Module() usbp = Signal() usbn = Signal() oe = Signal() # wait for new packet to start with m.FSM(domain="usb_io"): with m.State("IDLE"): m.d.comb += [ usbp.eq(1), usbn.eq(0), oe.eq(0), ] with m.If(self.i_valid & self.i_oe): # first bit of sync always forces a transition, we idle # in J so the first output bit is K. m.next = "DK" # the output line is in state J with m.State("DJ"): m.d.comb += [ usbp.eq(1), usbn.eq(0), oe.eq(1), ] with m.If(self.i_valid): with m.If(~self.i_oe): m.next = "SE0A" with m.Elif(self.i_data): m.next = "DJ" with m.Else(): m.next = "DK" # the output line is in state K with m.State("DK"): m.d.comb += [ usbp.eq(0), usbn.eq(1), oe.eq(1), ] with m.If(self.i_valid): with m.If(~self.i_oe): m.next = "SE0A" with m.Elif(self.i_data): m.next = "DK" with m.Else(): m.next = "DJ" # first bit of the SE0 state with m.State("SE0A"): m.d.comb += [ usbp.eq(0), usbn.eq(0), oe.eq(1), ] with m.If(self.i_valid): m.next = "SE0B" # second bit of the SE0 state with m.State("SE0B"): m.d.comb += [ usbp.eq(0), usbn.eq(0), oe.eq(1), ] with m.If(self.i_valid): m.next = "EOPJ" # drive the bus back to J before relinquishing control with m.State("EOPJ"): m.d.comb += [ usbp.eq(1), usbn.eq(0), oe.eq(1), ] with m.If(self.i_valid): m.next = "IDLE" m.d.usb_io += [ self.o_oe.eq(oe), self.o_usbp.eq(usbp), self.o_usbn.eq(usbn), ] return m
class USBDeviceController(Peripheral, Elaboratable): """ SoC controller for a USBDevice. Breaks our USBDevice control and status signals out into registers so a CPU / Wishbone master can control our USB device. The attributes below are intended to connect to a USBDevice. Typically, they'd be created by using the .controller() method on a USBDevice object, which will automatically connect all relevant signals. Attributes ---------- connect: Signal(), output High when the USBDevice should be allowed to connect to a host. """ def __init__(self): super().__init__() # # I/O port # self.connect = Signal(reset=1) self.bus_reset = Signal() # # Registers. # regs = self.csr_bank() self._connect = regs.csr(1, "rw", desc=""" Set this bit to '1' to allow the associated USB device to connect to a host. """) self._speed = regs.csr(2, "r", desc=""" Indicates the current speed of the USB device. 0 indicates High; 1 => Full, 2 => Low, and 3 => SuperSpeed (incl SuperSpeed+). """) self._reset_irq = self.event(name="reset", desc=""" Interrupt that occurs when a USB bus reset is received. """) # Wishbone connection. self._bridge = self.bridge(data_width=32, granularity=8, alignment=2) self.bus = self._bridge.bus self.irq = self._bridge.irq def attach(self, device: USBDevice): """ Returns a list of statements necessary to connect this to a USB controller. The returned values makes all of the connections necessary to provide control and fetch status from the relevant USB device. These can be made either combinationally or synchronously, but combinational is recommended; as these signals are typically fed from a register anyway. Parameters ---------- device: USBDevice The :class:`USBDevice` object to be controlled. """ return [ device.connect.eq(self.connect), self.bus_reset.eq(device.reset_detected), self._speed.r_data.eq(device.speed) ] def elaborate(self, platform): m = Module() m.submodules.bridge = self._bridge # Core connection register. m.d.comb += self.connect.eq(self._connect.r_data) with m.If(self._connect.w_stb): m.d.usb += self._connect.r_data.eq(self._connect.w_data) # Reset-detection event. m.d.comb += self._reset_irq.stb.eq(self.bus_reset) return m
class TxBitstuffer(Elaboratable): """ Bitstuff Insertion Long sequences of 1's would cause the receiver to lose it's lock on the transmitter's clock. USB solves this with bitstuffing. A '0' is stuffed after every 6 consecutive 1's. The TxBitstuffer is the only component in the transmit pipeline that can delay transmission of serial data. It is therefore responsible for generating the bit_strobe signal that keeps the pipe moving forward. https://www.pjrc.com/teensy/beta/usb20.pdf, USB2 Spec, 7.1.9 https://en.wikipedia.org/wiki/Bit_stuffing Clock Domain ------------ usb_12 : 48MHz Input Ports ------------ i_data : Signal() Data bit to be transmitted on USB. Output Ports ------------ o_data : Signal() Data bit to be transmitted on USB. o_stall : Signal() Used to apply backpressure on the tx pipeline. """ def __init__(self): self.i_data = Signal() self.o_stall = Signal() self.o_will_stall = Signal() self.o_data = Signal() def elaborate(self, platform): m = Module() stuff_bit = Signal() with m.FSM(domain="usb"): for i in range(5): with m.State(f"D{i}"): # Receiving '1' increments the bitstuff counter. with m.If(self.i_data): m.next = f"D{i+1}" # Receiving '0' resets the bitstuff counter. with m.Else(): m.next = "D0" with m.State("D5"): with m.If(self.i_data): # There's a '1', so indicate we might stall on the next loop. m.d.comb += self.o_will_stall.eq(1), m.next = "D6" with m.Else(): m.next = "D0" with m.State("D6"): m.d.comb += stuff_bit.eq(1) m.next = "D0" m.d.comb += [self.o_stall.eq(stuff_bit)] # flop outputs with m.If(stuff_bit): m.d.usb += self.o_data.eq(0), with m.Else(): m.d.usb += self.o_data.eq(self.i_data) return m
def elaborate(self, platform): self.m = m = Module() comb = m.d.comb sync = m.d.sync # CPU units used. logic = m.submodules.logic = LogicUnit() adder = m.submodules.adder = AdderUnit() shifter = m.submodules.shifter = ShifterUnit() compare = m.submodules.compare = CompareUnit() self.current_priv_mode = Signal(PrivModeBits, reset=PrivModeBits.MACHINE) csr_unit = self.csr_unit = m.submodules.csr_unit = CsrUnit( # TODO does '==' below produces the same synth result as .all()? in_machine_mode=self.current_priv_mode == PrivModeBits.MACHINE) exception_unit = self.exception_unit = m.submodules.exception_unit = ExceptionUnit( csr_unit=csr_unit, current_priv_mode=self.current_priv_mode) arbiter = self.arbiter = m.submodules.arbiter = MemoryArbiter( mem_config=self.mem_config, with_addr_translation=True, csr_unit=csr_unit, # SATP register exception_unit=exception_unit, # current privilege mode ) mem_unit = m.submodules.mem_unit = MemoryUnit(mem_port=arbiter.port( priority=0)) ibus = arbiter.port(priority=2) if self.with_debug: m.submodules.debug = self.debug = DebugUnit(self) self.debug_bus = arbiter.port(priority=1) # Current decoding state signals. instr = self.instr = Signal(32) funct3 = self.funct3 = Signal(3) funct7 = self.funct7 = Signal(7) rd = self.rd = Signal(5) rs1 = Signal(5) rs2 = Signal(5) rs1val = Signal(32) rs2val = Signal(32) rdval = Signal(32) # calculated by unit, stored to register file imm = Signal(signed(12)) csr_idx = Signal(12) uimm = Signal(20) opcode = self.opcode = Signal(InstrType) pc = self.pc = Signal(32, reset=CODE_START_ADDR) # at most one active_unit at any time active_unit = ActiveUnit() # Register file. Contains two read ports (for rs1, rs2) and one write port. regs = Memory(width=32, depth=32, init=self.reg_init) reg_read_port1 = m.submodules.reg_read_port1 = regs.read_port() reg_read_port2 = m.submodules.reg_read_port2 = regs.read_port() reg_write_port = (self.reg_write_port ) = m.submodules.reg_write_port = regs.write_port() # Timer management. mtime = self.mtime = Signal(32) sync += mtime.eq(mtime + 1) comb += csr_unit.mtime.eq(mtime) self.halt = Signal() with m.If(csr_unit.mstatus.mie & csr_unit.mie.mtie): with m.If(mtime == csr_unit.mtimecmp): # 'halt' signal needs to be cleared when CPU jumps to trap handler. sync += [ self.halt.eq(1), ] comb += [ exception_unit.m_instruction.eq(instr), exception_unit.m_pc.eq(pc), # TODO more ] # TODO # DebugModule is able to read and write GPR values. # if self.with_debug: # comb += self.halt.eq(self.debug.HALT) # else: # comb += self.halt.eq(0) # with m.If(self.halt): # comb += [ # reg_read_port1.addr.eq(self.gprf_debug_addr), # reg_write_port.addr.eq(self.gprf_debug_addr), # reg_write_port.en.eq(self.gprf_debug_write_en) # ] # with m.If(self.gprf_debug_write_en): # comb += reg_write_port.data.eq(self.gprf_debug_data) # with m.Else(): # comb += self.gprf_debug_data.eq(reg_read_port1.data) with m.If(0): pass with m.Else(): comb += [ reg_read_port1.addr.eq(rs1), reg_read_port2.addr.eq(rs2), reg_write_port.addr.eq(rd), reg_write_port.data.eq(rdval), # reg_write_port.en set later rs1val.eq(reg_read_port1.data), rs2val.eq(reg_read_port2.data), ] comb += [ # following is not true for all instrutions, but in specific cases will be overwritten later imm.eq(instr[20:32]), csr_idx.eq(instr[20:32]), uimm.eq(instr[12:]), ] # drive input signals of actually used unit. with m.If(active_unit.logic): comb += [ logic.funct3.eq(funct3), logic.src1.eq(rs1val), logic.src2.eq(Mux(opcode == InstrType.OP_IMM, imm, rs2val)), ] with m.Elif(active_unit.adder): comb += [ adder.src1.eq(rs1val), adder.src2.eq(Mux(opcode == InstrType.OP_IMM, imm, rs2val)), ] with m.Elif(active_unit.shifter): comb += [ shifter.funct3.eq(funct3), shifter.funct7.eq(funct7), shifter.src1.eq(rs1val), shifter.shift.eq( Mux(opcode == InstrType.OP_IMM, imm[0:5].as_unsigned(), rs2val[0:5])), ] with m.Elif(active_unit.mem_unit): comb += [ mem_unit.en.eq(1), mem_unit.funct3.eq(funct3), mem_unit.src1.eq(rs1val), mem_unit.src2.eq(rs2val), mem_unit.store.eq(opcode == InstrType.STORE), mem_unit.offset.eq( Mux(opcode == InstrType.LOAD, imm, Cat(rd, imm[5:12]))), ] with m.Elif(active_unit.compare): comb += [ compare.funct3.eq(funct3), # Compare Unit uses Adder for carry and overflow flags. adder.src1.eq(rs1val), adder.src2.eq(Mux(opcode == InstrType.OP_IMM, imm, rs2val)), # adder.sub set somewhere below ] with m.Elif(active_unit.branch): comb += [ compare.funct3.eq(funct3), # Compare Unit uses Adder for carry and overflow flags. adder.src1.eq(rs1val), adder.src2.eq(rs2val), # adder.sub set somewhere below ] with m.Elif(active_unit.csr): comb += [ csr_unit.func3.eq(funct3), csr_unit.csr_idx.eq(csr_idx), csr_unit.rs1.eq(rs1), csr_unit.rs1val.eq(rs1val), csr_unit.rd.eq(rd), csr_unit.en.eq(1), ] comb += [ compare.negative.eq(adder.res[-1]), compare.overflow.eq(adder.overflow), compare.carry.eq(adder.carry), compare.zero.eq(adder.res == 0), ] # Decoding state (with redundancy - instr. type not known yet). # We use 'ibus.read_data' instead of 'instr' (that is driven by sync domain) # for getting registers to save 1 cycle. comb += [ opcode.eq(instr[0:7]), rd.eq(instr[7:12]), funct3.eq(instr[12:15]), rs1.eq(instr[15:20]), rs2.eq(instr[20:25]), funct7.eq(instr[25:32]), ] def fetch_with_new_pc(pc: Signal): m.next = "FETCH" m.d.sync += active_unit.eq(0) m.d.sync += self.pc.eq(pc) def trap(cause: Optional[Union[TrapCause, IrqCause]], interrupt=False): fetch_with_new_pc(Cat(Const(0, 2), self.csr_unit.mtvec.base)) if cause is None: return assert isinstance(cause, TrapCause) or isinstance(cause, IrqCause) e = exception_unit notifiers = e.irq_cause_map if interrupt else e.trap_cause_map m.d.comb += notifiers[cause].eq(1) self.fetch = Signal() interconnect_error = Signal() comb += interconnect_error.eq(exception_unit.m_store_error | exception_unit.m_fetch_error | exception_unit.m_load_error) with m.FSM(): with m.State("FETCH"): with m.If(self.halt): sync += self.halt.eq(0) trap(IrqCause.M_TIMER_INTERRUPT, interrupt=True) with m.Else(): with m.If(pc & 0b11): trap(TrapCause.FETCH_MISALIGNED) with m.Else(): comb += [ ibus.en.eq(1), ibus.store.eq(0), ibus.addr.eq(pc), ibus.mask.eq(0b1111), ibus.is_fetch.eq(1), ] with m.If(interconnect_error): trap(cause=None) with m.If(ibus.ack): sync += [ instr.eq(ibus.read_data), ] m.next = "DECODE" with m.State("DECODE"): comb += self.fetch.eq( 1 ) # only for simulation, notify that 'instr' ready to use. m.next = "EXECUTE" # here, we have registers already fetched into rs1val, rs2val. with m.If(instr & 0b11 != 0b11): trap(TrapCause.ILLEGAL_INSTRUCTION) with m.If(match_logic_unit(opcode, funct3, funct7)): sync += [ active_unit.logic.eq(1), ] with m.Elif(match_adder_unit(opcode, funct3, funct7)): sync += [ active_unit.adder.eq(1), adder.sub.eq((opcode == InstrType.ALU) & (funct7 == Funct7.SUB)), ] with m.Elif(match_shifter_unit(opcode, funct3, funct7)): sync += [ active_unit.shifter.eq(1), ] with m.Elif(match_loadstore_unit(opcode, funct3, funct7)): sync += [ active_unit.mem_unit.eq(1), ] with m.Elif(match_compare_unit(opcode, funct3, funct7)): sync += [ active_unit.compare.eq(1), adder.sub.eq(1), ] with m.Elif(match_lui(opcode, funct3, funct7)): sync += [ active_unit.lui.eq(1), ] comb += [ reg_read_port1.addr.eq(rd), # rd will be available in next cycle in rs1val ] with m.Elif(match_auipc(opcode, funct3, funct7)): sync += [ active_unit.auipc.eq(1), ] with m.Elif(match_jal(opcode, funct3, funct7)): sync += [ active_unit.jal.eq(1), ] with m.Elif(match_jalr(opcode, funct3, funct7)): sync += [ active_unit.jalr.eq(1), ] with m.Elif(match_branch(opcode, funct3, funct7)): sync += [ active_unit.branch.eq(1), adder.sub.eq(1), ] with m.Elif(match_csr(opcode, funct3, funct7)): sync += [active_unit.csr.eq(1)] with m.Elif(match_mret(opcode, funct3, funct7)): sync += [active_unit.mret.eq(1)] with m.Elif(match_sfence_vma(opcode, funct3, funct7)): pass # sfence.vma with m.Elif(opcode == 0b0001111): pass # fence with m.Else(): trap(TrapCause.ILLEGAL_INSTRUCTION) with m.State("EXECUTE"): with m.If(active_unit.logic): sync += [ rdval.eq(logic.res), ] with m.Elif(active_unit.adder): sync += [ rdval.eq(adder.res), ] with m.Elif(active_unit.shifter): sync += [ rdval.eq(shifter.res), ] with m.Elif(active_unit.mem_unit): sync += [ rdval.eq(mem_unit.res), ] with m.Elif(active_unit.compare): sync += [ rdval.eq(compare.condition_met), ] with m.Elif(active_unit.lui): sync += [ rdval.eq(Cat(Const(0, 12), uimm)), ] with m.Elif(active_unit.auipc): sync += [ rdval.eq(pc + Cat(Const(0, 12), uimm)), ] with m.Elif(active_unit.jal | active_unit.jalr): sync += [ rdval.eq(pc + 4), ] with m.Elif(active_unit.csr): sync += [rdval.eq(csr_unit.rd_val)] # control flow mux - all traps need to be here, otherwise it will overwrite m.next statement. with m.If(active_unit.mem_unit): with m.If(mem_unit.ack): m.next = "WRITEBACK" sync += active_unit.eq(0) with m.Else(): m.next = "EXECUTE" with m.If(interconnect_error): # NOTE: # the order of that 'If' is important. # In case of error overwrite m.next above. trap(cause=None) with m.Elif(active_unit.csr): with m.If(csr_unit.illegal_insn): trap(TrapCause.ILLEGAL_INSTRUCTION) with m.Else(): with m.If(csr_unit.vld): m.next = "WRITEBACK" sync += active_unit.eq(0) with m.Else(): m.next = "EXECUTE" with m.Elif(active_unit.mret): comb += exception_unit.m_mret.eq(1) fetch_with_new_pc(exception_unit.mepc) with m.Else(): # all units not specified by default take 1 cycle m.next = "WRITEBACK" sync += active_unit.eq(0) jal_offset = Signal(signed(21)) comb += jal_offset.eq( Cat( Const(0, 1), instr[21:31], instr[20], instr[12:20], instr[31], ).as_signed()) pc_addend = Signal(signed(32)) sync += pc_addend.eq(Mux(active_unit.jal, jal_offset, 4)) branch_addend = Signal(signed(13)) comb += branch_addend.eq( Cat( Const(0, 1), instr[8:12], instr[25:31], instr[7], instr[31], ).as_signed() # TODO is it ok that it's signed? ) with m.If(active_unit.branch): with m.If(compare.condition_met): sync += pc_addend.eq(branch_addend) new_pc = Signal(32) is_jalr_latch = Signal() # that's bad workaround with m.If(active_unit.jalr): sync += is_jalr_latch.eq(1) sync += new_pc.eq(rs1val.as_signed() + imm) with m.State("WRITEBACK"): with m.If(is_jalr_latch): sync += pc.eq(new_pc) with m.Else(): sync += pc.eq(pc + pc_addend) sync += is_jalr_latch.eq(0) # Here, rdval is already calculated. If neccessary, put it into register file. should_write_rd = self.should_write_rd = Signal() writeback = self.writeback = Signal() # for riscv-dv simulation: # detect that instruction does not perform register write to avoid infinite loop # by checking writeback & should_write_rd # TODO it will break for trap-causing instructions. comb += writeback.eq(1) comb += should_write_rd.eq( reduce( or_, [ match_shifter_unit(opcode, funct3, funct7), match_adder_unit(opcode, funct3, funct7), match_logic_unit(opcode, funct3, funct7), match_load(opcode, funct3, funct7), match_compare_unit(opcode, funct3, funct7), match_lui(opcode, funct3, funct7), match_auipc(opcode, funct3, funct7), match_jal(opcode, funct3, funct7), match_jalr(opcode, funct3, funct7), match_csr(opcode, funct3, funct7), ], ) & (rd != 0)) with m.If(should_write_rd): comb += reg_write_port.en.eq(True) m.next = "FETCH" return m
def __init__(self): self.i_data = Signal() self.o_stall = Signal() self.o_will_stall = Signal() self.o_data = Signal()
class ValueAddressGenerator(SimpleElaboratable): """Generates addresses within a single pixel. Specifically for a 4x4 2DConv, works with a PixelAddressGenerator to find addresses of individual values. It reads `depth` * 4 * 32bit words, then moves down and repeats this. An external counter re-starts the generator once it has read 4 complete rows of input. Attributes ---------- start: Signal, in Begin generating addresses from the start_addr. start_addr: Signal(14), in Address of start of first pixel, from a PixelAddressGenerator. depth: Signal(3), in Number of 16-byte blocks to read per pixel. Max depth is 7 which is 112 values/pixel). num_blocks_y: Signal(10), in Number of blocks per row. addr_out: Signal(14), out Current output address """ def __init__(self): self.start = Signal() self.start_addr = Signal(14) self.depth = Signal(3) self.num_blocks_y = Signal(10) self.addr_out = Signal(14) def elab(self, m): x_count = Signal(7) next_row_addr = Signal(14) addr = Signal(14) with m.If(self.start): # Start overrides other behaviors m.d.comb += self.addr_out.eq(self.start_addr) m.d.sync += [ addr.eq(self.start_addr), x_count.eq(1), next_row_addr.eq(self.start_addr + self.num_blocks_y), ] with m.Else(): m.d.comb += self.addr_out.eq(addr) # x_size is the number of cycles to read 4 consecutive pixels x_size = Signal(7) m.d.comb += x_size.eq(self.depth << 4) with m.If(x_count != (x_size - 1)): m.d.sync += x_count.eq(x_count + 1) with m.If(x_count[:2] == 3): m.d.sync += addr.eq(addr + 1) with m.Else(): # x_count == x_size - 1 ==> End of row m.d.sync += [ addr.eq(next_row_addr), next_row_addr.eq(next_row_addr + self.num_blocks_y), x_count.eq(0), ]
class USBOutStreamBoundaryDetector(Elaboratable): """ Gateware that detects USBOutStream packet boundaries, and generates First and Last signals. As UTMI/ULPI do not denote the last byte of a packet; this module injects two bytes of delay in order to correctly identify the last bytes. Attributes ---------- unprocessed_stream: USBOutStreamInterface, input stream The stream to work with; will be processed and then output on :attr:``processed_stream``. processed_stream: USBOutStreamInterface, output stream The stream produced by this module. This stream is two bytes delayed from :attr:``unprocessed_stream``; and in-phase with the :attr::``first`` and :attr::``last`` signals. complete_in: Signal(), input, optional Input that accepts an RxComplete signal. If provided; a delayed version will be produced on :attr:``complete_out`` after a :attr:``processed_stream`` packet terminates. invalid_in: Signal(), input, optional Input that accepts an RxInvalid signal. If provided; a delayed version will be produced on :attr:``complete_out`` after a :attr:``processed_stream`` packet terminates. complete_out: Signal(), output If :attr:``complete_in`` is provided; this signal provides a delayed version of that signal timed so it is strobed after :attr:``processed_stream`` packets complete. invalid_out: Signal(), output If :attr:``invalid_out`` is provided; this signal provides a delayed version of that signal timed so it is strobed after :attr:``processed_stream`` packets complete. first: Signal(), output Indicates that the byte present on :attr:``processed_stream`` is the first byte of a packet. last: Signal(), output Indicates that the byte present on :attr:``processed_stream`` is the last byte of a packet. Parameters ---------- domain: str The name of the domain the stream belongs to; defaults to "usb". """ def __init__(self, domain="usb"): self._domain = domain # # I/O port # self.unprocessed_stream = USBOutStreamInterface() self.processed_stream = USBOutStreamInterface() self.complete_in = Signal() self.invalid_in = Signal() self.complete_out = Signal() self.invalid_out = Signal() self.first = Signal() self.last = Signal() def elaborate(self, platform): m = Module() in_stream = self.unprocessed_stream out_stream = self.processed_stream # We'll buffer a single byte of the stream, so we can always be one byte ahead. buffered_byte = Signal(8) is_first_byte = Signal() buffered_complete = Signal() buffered_invalid = Signal() with m.FSM(domain='usb'): # WAIT_FOR_FIRST_BYTE -- we're not actively receiving data, yet. Wait for the # first byte of a new packet. with m.State('WAIT_FOR_FIRST_BYTE'): m.d.usb += out_stream.valid.eq(0) m.d.usb += [ # We have no data to output, so this can't be our first or last bytes... self.first.eq(0), self.last.eq(0), out_stream.next.eq(0), # ... and we can't have gotten a complete or invalid strobe that matters to us. buffered_complete.eq(0), buffered_invalid.eq(0), self.complete_out.eq(0), self.invalid_out.eq(0), ] # Once we've received our first byte, buffer it, and mark it as our first byte. with m.If(in_stream.valid & in_stream.next): m.d.usb += [ buffered_byte.eq(in_stream.payload), is_first_byte.eq(1) ] m.next = 'RECEIVE_AND_TRANSMIT' # RECEIVE_AND_TRANSMIT -- receive incoming bytes, and transmit our buffered bytes. # We'll transmit one byte per byte received; ensuring we always retain a single byte -- # our last byte. with m.State('RECEIVE_AND_TRANSMIT'): m.d.usb += [out_stream.valid.eq(1), out_stream.next.eq(0)] # Buffer any complete/invalid signals we get while receiving, so we don't output # them before we finish outputting our processed stream. m.d.usb += [ buffered_complete.eq(buffered_complete | self.complete_in), buffered_invalid.eq(buffered_invalid | self.invalid_in) ] # If we get a new byte, emit our buffered byte, and store the incoming byte. with m.If(in_stream.valid & in_stream.next): m.d.usb += [ # Output our buffered byte... out_stream.payload.eq(buffered_byte), out_stream.next.eq(1), # indicate whether our current byte was the first byte captured... self.first.eq(is_first_byte), # ... and store the new, incoming byte. buffered_byte.eq(in_stream.payload), is_first_byte.eq(0) ] # Once we no longer have an active packet, transmit our _last_ byte, # and move back to waiting for an active packet. with m.If(~in_stream.valid): m.d.usb += [ # Output our buffered byte... out_stream.payload.eq(buffered_byte), out_stream.next.eq(1), self.first.eq(is_first_byte), # ... and indicate that it's the last byte in our stream. self.last.eq(1) ] m.next = 'OUTPUT_STROBES' with m.State('OUTPUT_STROBES'): m.d.usb += [ # We've just finished transmitting our processed stream; so clear our data strobes... self.first.eq(0), self.last.eq(0), out_stream.next.eq(0), # ... and output our buffered complete/invalid strobes. self.complete_out.eq(buffered_complete), self.invalid_out.eq(buffered_invalid) ] m.next = 'WAIT_FOR_FIRST_BYTE' if self._domain != "usb": m = DomainRenamer({"usb": self._domain})(m) return m