def build_param_store(self, m): # Create memory for post process params # Use SinglePortMemory here? param_mem = Memory(width=POST_PROCESS_PARAMS_WIDTH, depth=Constants.MAX_CHANNEL_DEPTH) m.submodules['param_rp'] = rp = param_mem.read_port(transparent=False) m.submodules['param_wp'] = wp = param_mem.write_port() # Configure param writer m.submodules['param_writer'] = pw = ParamWriter() m.d.comb += connect(self.post_process_params, pw.input_data) m.d.comb += [ pw.reset.eq(self.reset), wp.en.eq(pw.mem_we), wp.addr.eq(pw.mem_addr), wp.data.eq(pw.mem_data), ] # Configure param reader m.submodules['param_reader'] = reader = ReadingProducer() repeats = Mux(self.config.mode, Constants.SYS_ARRAY_HEIGHT, Constants.SYS_ARRAY_HEIGHT // 2) m.d.comb += [ # Reset reader whenever new parameters are written reader.reset.eq(pw.input_data.is_transferring()), reader.sizes.depth.eq(self.config.output_channel_depth), reader.sizes.repeats.eq(repeats), rp.addr.eq(reader.mem_addr), reader.mem_data.eq(rp.data), ] return reader.output_data
def make_ram(n, init): mem = Memory(width=32, depth=4, init=init) self.m.submodules[f"rp{n}"] = rp = mem.read_port(transparent=False) self.m.d.comb += [ ram_mux.lram_data[n].eq(rp.data), rp.addr.eq(ram_mux.lram_addr[n]), rp.en.eq(1), ram_mux.addr_in[n].eq(reader.ram_mux_addr[n]), reader.ram_mux_data[n].eq(ram_mux.data_out[n]), ]
def elab(self, m): mem = Memory(width=self.width, depth=self.depth, simulate=self.is_sim) m.submodules['wp'] = wp = mem.write_port() m.submodules['rp'] = rp = mem.read_port(transparent=False) m.d.comb += [ wp.en.eq(self.w_en), wp.addr.eq(self.w_addr), wp.data.eq(self.w_data), rp.en.eq(1), rp.addr.eq(self.r_addr), self.r_data.eq(rp.data), ]
def elab(self, m): memory = Memory(width=self._data_width, depth=self._depth) m.submodules.rp = rp = memory.read_port(transparent=False) m.submodules.wp = wp = memory.write_port() m.d.comb += [ wp.en.eq(self.write_enable), rp.en.eq(~self.write_enable), wp.addr.eq(self.write_addr), wp.data.eq(self.write_data), rp.addr.eq(self.read_addr), self.read_data.eq(rp.data) ]
def elab(self, m: Module): rp_data = [] for i in range(4): mem = Memory(depth=self._depth // 4, width=32) # Set up read port m.submodules[f'rp_{i}'] = rp = mem.read_port(transparent=False) rp_data.append(rp.data) m.d.comb += rp.addr.eq(self.read_addr) m.d.comb += rp.en.eq(~self.write_enable) # Set up write port m.submodules[f'wp_{i}'] = wp = mem.write_port() m.d.comb += wp.addr.eq(self.write_addr[2:]) m.d.comb += wp.data.eq(self.write_data) m.d.comb += wp.en.eq(self.write_enable & (i == self.write_addr[:2])) # Assemble all of the read port outputs into one, 128bit wide signal m.d.comb += self.read_data.eq(Cat(rp_data))
def create_dut(self): fetcher = Mode1InputFetcher() # Connect RAM Mux with simulated LRAMs self.m.submodules["ram_mux"] = ram_mux = RamMux() self.m.d.comb += ram_mux.phase.eq(fetcher.ram_mux_phase) for i in range(4): padding = [0] * (self.BASE_ADDR // 16) init = (padding + self.data.input_data[i::4])[:1024] mem = Memory(width=32, depth=1024, init=init) rp = mem.read_port(transparent=False) self.m.d.comb += [ ram_mux.lram_data[i].eq(rp.data), rp.addr.eq(ram_mux.lram_addr[i]), rp.en.eq(1), ram_mux.addr_in[i].eq(fetcher.ram_mux_addr[i]), fetcher.ram_mux_data[i].eq(ram_mux.data_out[i]), ] self.m.submodules[f"rp{i}"] = rp return fetcher
class Memtest(Elaboratable): def __init__(self): self.mem = Memory(width=MEMWIDTH, depth=Firestarter.memdepth) def elaborate(self, platform): m = Module() led0 = platform.request("led", 0) led1 = platform.request("led", 1) m.submodules.rdport = rdport = self.mem.read_port() with m.If(rdport.data == 255): m.d.comb += led0.eq(0) with m.Else(): m.d.comb += led0.eq(1) timer = Signal(range(round(100E6))) with m.If(timer > 100): m.d.comb += rdport.addr.eq(100) with m.Else(): m.d.comb += rdport.addr.eq(0) m.d.sync += timer.eq(timer + 1) m.d.comb += led1.o.eq(timer[-1]) return m
class USBAnalyzer(Elaboratable): """ Core USB analyzer; backed by a small ringbuffer in FPGA block RAM. If you're looking to instantiate a full analyzer, you'll probably want to grab one of the DRAM-based ringbuffer variants (which are currently forthcoming). If you're looking to use this with a ULPI PHY, rather than the FPGA-convenient UTMI interface, grab the UTMITranslator from `luna.gateware.interface.ulpi`. Attributes ---------- stream: StreamInterface(), output stream Stream that carries USB analyzer data. idle: Signal(), output Asserted iff the analyzer is not currently receiving data. overrun: Signal(), output Asserted iff the analyzer has received more data than it can store in its internal buffer. Occurs if :attr:``stream`` is not being read quickly enough. capturing: Signal(), output Asserted iff the analyzer is currently capturing a packet. Parameters ---------- utmi_interface: UTMIInterface() The UTMI interface that carries the data to be analyzed. mem_depth: int, default=8192 The depth of the analyzer's local ringbuffer, in bytes. Must be a power of 2. """ # Current, we'll provide a packet header of 16 bits. HEADER_SIZE_BITS = 16 HEADER_SIZE_BYTES = HEADER_SIZE_BITS // 8 # Support a maximum payload size of 1024B, plus a 1-byte PID and a 2-byte CRC16. MAX_PACKET_SIZE_BYTES = 1024 + 1 + 2 def __init__(self, *, utmi_interface, mem_depth=8192): """ Parameters: utmi_interface -- A record or elaboratable that presents a UTMI interface. """ self.utmi = utmi_interface assert (mem_depth % 2) == 0, "mem_depth must be a power of 2" # Internal storage memory. self.mem = Memory(width=8, depth=mem_depth, name="analysis_ringbuffer") self.mem_size = mem_depth # # I/O port # self.stream = StreamInterface() self.idle = Signal() self.overrun = Signal() self.capturing = Signal() # Diagnostic I/O. self.sampling = Signal() def elaborate(self, platform): m = Module() # Memory read and write ports. m.submodules.read = mem_read_port = self.mem.read_port(domain="usb") m.submodules.write = mem_write_port = self.mem.write_port(domain="usb") # Store the memory address of our active packet header, which will store # packet metadata like the packet size. header_location = Signal.like(mem_write_port.addr) write_location = Signal.like(mem_write_port.addr) # Read FIFO status. read_location = Signal.like(mem_read_port.addr) fifo_count = Signal.like(mem_read_port.addr, reset=0) fifo_new_data = Signal() # Current receive status. packet_size = Signal(16) # # Read FIFO logic. # m.d.comb += [ # We have data ready whenever there's data in the FIFO. self.stream.valid.eq((fifo_count != 0) & self.idle), # Our data_out is always the output of our read port... self.stream.payload.eq(mem_read_port.data), self.sampling.eq(mem_write_port.en) ] # Once our consumer has accepted our current data, move to the next address. with m.If(self.stream.ready & self.stream.valid): m.d.usb += read_location.eq(read_location + 1) m.d.comb += mem_read_port.addr.eq(read_location + 1) with m.Else(): m.d.comb += mem_read_port.addr.eq(read_location), # # FIFO count handling. # fifo_full = (fifo_count == self.mem_size) data_pop = Signal() data_push = Signal() m.d.comb += [ data_pop.eq(self.stream.ready & self.stream.valid), data_push.eq(fifo_new_data & ~fifo_full) ] # If we have both a read and a write, don't update the count, # as we've both added one and subtracted one. with m.If(data_push & data_pop): pass # Otherwise, add when data's added, and subtract when data's removed. with m.Elif(data_push): m.d.usb += fifo_count.eq(fifo_count + 1) with m.Elif(data_pop): m.d.usb += fifo_count.eq(fifo_count - 1) # # Core analysis FSM. # with m.FSM(domain="usb") as f: m.d.comb += [ self.idle.eq(f.ongoing("IDLE")), self.overrun.eq(f.ongoing("OVERRUN")), self.capturing.eq(f.ongoing("CAPTURE")), ] # IDLE: wait for an active receive. with m.State("IDLE"): # Wait until a transmission is active. # TODO: add triggering logic? with m.If(self.utmi.rx_active): m.next = "CAPTURE" m.d.usb += [ header_location.eq(write_location), write_location.eq(write_location + self.HEADER_SIZE_BYTES), packet_size.eq(0), ] # Capture data until the packet is complete. with m.State("CAPTURE"): byte_received = self.utmi.rx_valid & self.utmi.rx_active # Capture data whenever RxValid is asserted. m.d.comb += [ mem_write_port.addr.eq(write_location), mem_write_port.data.eq(self.utmi.rx_data), mem_write_port.en.eq(byte_received), fifo_new_data.eq(byte_received), ] # Advance the write pointer each time we receive a bit. with m.If(byte_received): m.d.usb += [ write_location.eq(write_location + 1), packet_size.eq(packet_size + 1) ] # If this would be filling up our data memory, # move to the OVERRUN state. with m.If(fifo_count == self.mem_size - 1 - self.HEADER_SIZE_BYTES): m.next = "OVERRUN" # If we've stopped receiving, move to the "finalize" state. with m.If(~self.utmi.rx_active): m.next = "EOP_1" # Optimization: if we didn't receive any data, there's no need # to create a packet. Clear our header from the FIFO and disarm. with m.If(packet_size == 0): m.next = "IDLE" m.d.usb += [write_location.eq(header_location)] with m.Else(): m.next = "EOP_1" # EOP: handle the end of the relevant packet. with m.State("EOP_1"): # Now that we're done, add the header to the start of our packet. # This will take two cycles, currently, as we're using a 2-byte header, # but we only have an 8-bit write port. m.d.comb += [ mem_write_port.addr.eq(header_location), mem_write_port.data.eq(packet_size[8:16]), mem_write_port.en.eq(1), fifo_new_data.eq(1) ] m.next = "EOP_2" with m.State("EOP_2"): # Add the second byte of our header. # Note that, if this is an adjacent read, we should have # just captured our packet header _during_ the stop turnaround. m.d.comb += [ mem_write_port.addr.eq(header_location + 1), mem_write_port.data.eq(packet_size[0:8]), mem_write_port.en.eq(1), fifo_new_data.eq(1) ] m.next = "IDLE" # BABBLE -- handles the case in which we've received a packet beyond # the allowable size in the USB spec with m.State("BABBLE"): # Trap here, for now. pass with m.State("OVERRUN"): # TODO: we should probably set an overrun flag and then emit an EOP, here? pass 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
class ADATTransmitter(Elaboratable): """transmit ADAT from a multiplexed stream of eight audio channels Parameters ---------- fifo_depth: capacity of the FIFO containing the ADAT frames to be transmitted Attributes ---------- adat_out: Signal the ADAT signal to be transmitted by the optical transmitter This input is unused at the moment. Instead the caller needs to ensure addr_in: Signal contains the ADAT channel number (0-7) of the current sample to be written into the currently assembled ADAT frame sample_in: Signal the 24 bit sample to be written into the channel slot given by addr_in in the currently assembled ADAT frame. The samples need to be committed in order of channel number (0-7) user_data_in: Signal the user data bits of the currently assembled frame. Will be committed, when ``last_in`` is strobed high valid_in: Signal commits the data at sample_in into the currently assembled frame, but only if ``ready_out`` is high ready_out: Signal outputs if there is space left in the transmit FIFO. It also will prevent any samples to be committed into the currently assembled ADAT frame last_in: Signal needs to be strobed when the last sample has been committed into the currently assembled ADAT frame. This will commit the user bits to the current ADAT frame fifo_level_out: Signal outputs the number of entries in the transmit FIFO underflow_out: Signal this underflow indicator will be strobed, when a new ADAT frame needs to be transmitted but the transmit FIFO is empty. In this case, the last ADAT frame will be transmitted again. """ def __init__(self, fifo_depth=9*4): self._fifo_depth = fifo_depth self.adat_out = Signal() self.addr_in = Signal(3) self.sample_in = Signal(24) self.user_data_in = Signal(4) self.valid_in = Signal() self.ready_out = Signal() self.last_in = Signal() self.fifo_level_out = Signal(range(fifo_depth+1)) self.underflow_out = Signal() self.mem = Memory(width=24, depth=8, name="sample_buffer") @staticmethod def chunks(lst: list, n: int): """Yield successive n-sized chunks from lst.""" for i in range(0, len(lst), n): yield lst[i:i + n] def elaborate(self, platform) -> Module: m = Module() sync = m.d.sync adat = m.d.adat comb = m.d.comb samples_write_port = self.mem.write_port() samples_read_port = self.mem.read_port(domain='comb') m.submodules += [samples_write_port, samples_read_port] # the highest bit in the FIFO marks a frame border frame_border_flag = 24 m.submodules.transmit_fifo = transmit_fifo = AsyncFIFO(width=25, depth=self._fifo_depth, w_domain="sync", r_domain="adat") # needed for output processing m.submodules.nrzi_encoder = nrzi_encoder = NRZIEncoder() transmitted_frame = Signal(30) transmit_counter = Signal(5) comb += [ self.ready_out .eq(transmit_fifo.w_rdy), self.fifo_level_out .eq(transmit_fifo.w_level), self.adat_out .eq(nrzi_encoder.nrzi_out), nrzi_encoder.data_in .eq(transmitted_frame.bit_select(transmit_counter, 1)), self.underflow_out .eq(0) ] # # Fill the transmit FIFO in the sync domain # channel_counter = Signal(3) # make sure, en is only asserted when explicitly strobed comb += samples_write_port.en.eq(0) write_frame_border = [ transmit_fifo.w_data .eq((1 << frame_border_flag) | self.user_data_in), transmit_fifo.w_en .eq(1) ] with m.FSM(): with m.State("DATA"): with m.If(self.ready_out): with m.If(self.valid_in): comb += [ samples_write_port.data.eq(self.sample_in), samples_write_port.addr.eq(self.addr_in), samples_write_port.en.eq(1) ] with m.If(self.last_in): sync += channel_counter.eq(0) comb += write_frame_border m.next = "COMMIT" # underflow: repeat last frame with m.Elif(transmit_fifo.w_level == 0): sync += channel_counter.eq(0) comb += self.underflow_out.eq(1) comb += write_frame_border m.next = "COMMIT" with m.State("COMMIT"): with m.If(transmit_fifo.w_rdy): comb += [ self.ready_out.eq(0), samples_read_port.addr .eq(channel_counter), transmit_fifo.w_data .eq(samples_read_port.data), transmit_fifo.w_en .eq(1) ] sync += channel_counter.eq(channel_counter + 1) with m.If(channel_counter == 7): m.next = "DATA" # # Read the FIFO and send data in the adat domain # # 4b/5b coding: Every 24 bit channel has 6 nibbles. # 1 bit before the sync pad and one bit before the user data nibble filler_bits = [Const(1, 1) for _ in range(7)] adat += transmit_counter.eq(transmit_counter - 1) comb += transmit_fifo.r_en.eq(0) with m.If(transmit_counter == 0): with m.If(transmit_fifo.r_rdy): comb += transmit_fifo.r_en.eq(1) with m.If(transmit_fifo.r_data[frame_border_flag] == 0): adat += [ transmit_counter.eq(29), # generate the adat data for one channel 0b1dddd1dddd1dddd1dddd1dddd1dddd where d is the PCM audio data transmitted_frame.eq(Cat(zip(list(self.chunks(transmit_fifo.r_data[:25], 4)), filler_bits))) ] with m.Else(): adat += [ transmit_counter.eq(15), # generate the adat sync_pad along with the user_bits 0b100000000001uuuu where u is user_data transmitted_frame.eq((1 << 15) | (1 << 4) | transmit_fifo.r_data[:5]) ] with m.Else(): # this should not happen: panic / stop transmitting. adat += [ transmitted_frame.eq(0x00), transmit_counter.eq(4) ] return m
class IntegratedLogicAnalyzer(Elaboratable): """ Super-simple integrated-logic-analyzer generator class for LUNA. Attributes ---------- trigger: Signal(), input A strobe that determines when we should start sampling. sampling: Signal(), output Indicates when sampling is in progress. complete: Signal(), output Indicates when sampling is complete and ready to be read. captured_sample_number: Signal(), input Selects which sample the ILA will output. Effectively the address for the ILA's sample buffer. captured_sample: Signal(), output The sample corresponding to the relevant sample number. Can be broken apart by using Cat(*signals). Parameters ---------- signals: iterable of Signals An iterable of signals that should be captured by the ILA. sample_depth: int The depth of the desired buffer, in samples. domain: string The clock domain in which the ILA should operate. sample_rate: float Cosmetic indication of the sample rate. Used to format output. samples_pretrigger: int The number of our samples which should be captured _before_ the trigger. This also can act like an implicit synchronizer; so asynchronous inputs are allowed if this number is >= 2. Note that the trigger strobe is read on the rising edge of the clock. """ def __init__(self, *, signals, sample_depth, domain="sync", sample_rate=60e6, samples_pretrigger=1): self.domain = domain self.signals = signals self.inputs = Cat(*signals) self.sample_width = len(self.inputs) self.sample_depth = sample_depth self.samples_pretrigger = samples_pretrigger self.sample_rate = sample_rate self.sample_period = 1 / sample_rate # # Create a backing store for our samples. # self.mem = Memory(width=self.sample_width, depth=sample_depth, name="ila_buffer") # # I/O port # self.trigger = Signal() self.sampling = Signal() self.complete = Signal() self.captured_sample_number = Signal(range(0, self.sample_depth)) self.captured_sample = Signal(self.sample_width) def elaborate(self, platform): m = Module() # Memory ports. write_port = self.mem.write_port() read_port = self.mem.read_port(domain="sync") m.submodules += [write_port, read_port] # If necessary, create synchronized versions of the relevant signals. if self.samples_pretrigger >= 2: delayed_inputs = Signal.like(self.inputs) m.submodules += FFSynchronizer(self.inputs, delayed_inputs, stages=self.samples_pretrigger) elif self.samples_pretrigger == 1: delayed_inputs = Signal.like(self.inputs) m.d.sync += delayed_inputs.eq(self.inputs) else: delayed_inputs = self.inputs # Counter that keeps track of our write position. write_position = Signal(range(0, self.sample_depth)) # Set up our write port to capture the input signals, # and our read port to provide the output. m.d.comb += [ write_port.data.eq(delayed_inputs), write_port.addr.eq(write_position), self.captured_sample.eq(read_port.data), read_port.addr.eq(self.captured_sample_number) ] # Don't sample unless our FSM asserts our sample signal explicitly. m.d.sync += write_port.en.eq(0) with m.FSM(name="ila_state") as fsm: m.d.comb += self.sampling.eq(~fsm.ongoing("IDLE")) # IDLE: wait for the trigger strobe with m.State('IDLE'): with m.If(self.trigger): m.next = 'SAMPLE' # Grab a sample as our trigger is asserted. m.d.sync += [ write_port.en.eq(1), write_position.eq(0), self.complete.eq(0), ] # SAMPLE: do our sampling with m.State('SAMPLE'): # Sample until we run out of samples. m.d.sync += [ write_port.en.eq(1), write_position.eq(write_position + 1), ] # If this is the last sample, we're done. Finish up. with m.If(write_position + 1 == self.sample_depth): m.next = "IDLE" m.d.sync += [self.complete.eq(1), write_port.en.eq(0)] # Convert our sync domain to the domain requested by the user, if necessary. if self.domain != "sync": m = DomainRenamer(self.domain)(m) return m
def elaborate(self, platform): m = Module() # Range shortcuts for internal signals. address_range = range(0, self.depth + 1) # # Core internal "backing store". # memory = Memory(width=self.width, depth=self.depth + 1, name=self.name) m.submodules.read_port = read_port = memory.read_port() m.submodules.write_port = write_port = memory.write_port() # Always connect up our memory's data/en ports to ours. m.d.comb += [ self.read_data.eq(read_port.data), write_port.data.eq(self.write_data), write_port.en.eq(self.write_en & ~self.full) ] # # Write port. # # We'll track two pieces of data: our _committed_ write position, and our current un-committed write one. # This will allow us to rapidly backtrack to our pre-commit position. committed_write_pointer = Signal(address_range) current_write_pointer = Signal(address_range) m.d.comb += write_port.addr.eq(current_write_pointer) # Compute the location for the next write, accounting for wraparound. We'll not assume a binary-sized # buffer; so we'll compute the wraparound manually. next_write_pointer = Signal.like(current_write_pointer) with m.If(current_write_pointer == self.depth): m.d.comb += next_write_pointer.eq(0) with m.Else(): m.d.comb += next_write_pointer.eq(current_write_pointer + 1) # If we're writing to the fifo, update our current write position. with m.If(self.write_en & ~self.full): m.d.sync += current_write_pointer.eq(next_write_pointer) # If we're committing a FIFO write, update our committed position. with m.If(self.write_commit): m.d.sync += committed_write_pointer.eq(current_write_pointer) # If we're discarding our current write, reset our current position, with m.If(self.write_discard): m.d.sync += current_write_pointer.eq(committed_write_pointer) # # Read port. # # We'll track two pieces of data: our _committed_ read position, and our current un-committed read one. # This will allow us to rapidly backtrack to our pre-commit position. committed_read_pointer = Signal(address_range) current_read_pointer = Signal(address_range) # Compute the location for the next read, accounting for wraparound. We'll not assume a binary-sized # buffer; so we'll compute the wraparound manually. next_read_pointer = Signal.like(current_read_pointer) with m.If(current_read_pointer == self.depth): m.d.comb += next_read_pointer.eq(0) with m.Else(): m.d.comb += next_read_pointer.eq(current_read_pointer + 1) # Our memory always takes a single cycle to provide its read output; so we'll update its address # "one cycle in advance". Accordingly, if we're about to advance the FIFO, we'll use the next read # address as our input. If we're not, we'll use the current one. with m.If(self.read_en & ~self.empty): m.d.comb += read_port.addr.eq(next_read_pointer) with m.Else(): m.d.comb += read_port.addr.eq(current_read_pointer) # If we're reading from our the fifo, update our current read position. with m.If(self.read_en & ~self.empty): m.d.sync += current_read_pointer.eq(next_read_pointer) # If we're committing a FIFO write, update our committed position. with m.If(self.read_commit): m.d.sync += committed_read_pointer.eq(current_read_pointer) # If we're discarding our current write, reset our current position, with m.If(self.read_discard): m.d.sync += current_read_pointer.eq(committed_read_pointer) # # FIFO status. # # Our FIFO is empty if our read and write pointers are in the same. We'll use the current # read position (which leads ahead) and the committed write position (which lags behind). m.d.comb += self.empty.eq( current_read_pointer == committed_write_pointer) # For our space available, we'll use the current write position (which leads ahead) and our committed # read position (which lags behind). This yields two cases: one where the buffer isn't wrapped around, # and one where it is. with m.If(self.full): m.d.comb += self.space_available.eq(0) with m.Elif(committed_read_pointer <= current_write_pointer): m.d.comb += self.space_available.eq(self.depth - (current_write_pointer - committed_read_pointer)) with m.Else(): m.d.comb += self.space_available.eq(committed_read_pointer - current_write_pointer - 1) # Our FIFO is full if we don't have any space available. m.d.comb += self.full.eq(next_write_pointer == committed_read_pointer) # If we're not supposed to be in the sync domain, rename our sync domain to the target. if self.domain != "sync": m = DomainRenamer({"sync": self.domain})(m) return m