def __init__(self, clkspertick, clkfreq, bits=64): self.clkspertick = int(clkfreq / clkspertick) self.intro = ModuleDoc("""TickTimer: A practical systick timer. TIMER0 in the system gives a high-resolution, sysclk-speed timer which overflows very quickly and requires OS overhead to convert it into a practically usable time source which counts off in systicks, instead of sysclks. The hardware parameter to the block is the divisor of sysclk, and sysclk. So if the divisor is 1000, then the increment for a tick is 1ms. If the divisor is 2000, the increment for a tick is 0.5ms. """) self.note = ModuleDoc( title="Configuration", body= "This timer was configured with {} bits, which rolls over in {:.2f} years, with each bit giving {}ms resolution" .format(bits, (2**bits / (60 * 60 * 24 * 365)) * (self.clkspertick / clkfreq), 1000 * (self.clkspertick / clkfreq))) prescaler = Signal(max=self.clkspertick, reset=self.clkspertick) timer = Signal(bits) # offer up to 48 bits of system time self.control = CSRStorage( 2, fields=[ CSRField("reset", description= "Write a `1` to this bit to reset the count to 0", pulse=True), CSRField( "pause", description= "Write a `1` to this field to pause counting, 0 for free-run" ) ]) self.time = CSRStatus(bits, name="time", description="""Elapsed time in systicks""") self.sync += [ If( self.control.fields.reset, timer.eq(0), prescaler.eq(self.clkspertick), ).Else( If(prescaler == 0, prescaler.eq(self.clkspertick), If( self.control.fields.pause == 0, timer.eq(timer + 1), )).Else(prescaler.eq(prescaler - 1), )) ] self.comb += self.time.status.eq(timer)
def __init__(self, parent, offsets=None): table = "" if offsets is not None: arr = [["Image", "Offset"]] for i, offset in enumerate(offsets): arr.append([str(i), str(offset)]) table = "\nYou can use this block to reboot into one of these four addresses:\n\n" \ + lxsocdoc.rst.make_table(arr) self.intro = ModuleDoc("""FPGA Reboot Interface This module provides the ability to reboot the FPGA. It is based on the ``SB_WARMBOOT`` primitive built in to the FPGA. When power is applied to the FPGA, it reads configuration data from the onboard flash chip. This contains reboot offsets for four images. It then booted from the first image, but kept note of the other addresses. {}""".format(table)) self.ctrl = CSRStorage(fields=[ CSRField("image", size=2, description=""" Which image to reboot to. ``SB_WARMBOOT`` supports four images that are configured at FPGA startup. The bootloader is image 0, so set these bits to 0 to reboot back into the bootloader. """), CSRField("key", size=6, description=""" A reboot key used to prevent accidental reboots when writing to random areas of memory. To initiate a reboot, set this to ``0b101011``.""" ) ], description=""" Provides support for rebooting the FPGA. You can select which of the four images to reboot to, just be sure to OR the image number with ``0xac``. For example, to reboot to the bootloader (image 0), write ``0xac``` to this register.""" ) self.addr = CSRStorage(size=32, description=""" This sets the reset vector for the VexRiscv. This address will be used whenever the CPU is reset, for example through a debug bridge. You should update this address whenever you load a new program, to enable the debugger to run ``mon reset`` """) do_reset = Signal() self.comb += [ # "Reset Key" is 0xac (0b101011xx) do_reset.eq(self.ctrl.storage[2] & self.ctrl.storage[3] & ~self.ctrl.storage[4] & self.ctrl.storage[5] & ~self.ctrl.storage[6] & self.ctrl.storage[7]) ] self.specials += Instance( "SB_WARMBOOT", i_S0=self.ctrl.storage[0], i_S1=self.ctrl.storage[1], i_BOOT=do_reset, ) parent.config["BITSTREAM_SYNC_HEADER1"] = 0x7e99aa7e parent.config["BITSTREAM_SYNC_HEADER2"] = 0x7eaa997e
def __init__(self, platform): self.intro = ModuleDoc("""Test for bitstream insertion of BRAM initialization contents""") platform.toolchain.attr_translate["KEEP"] = ("KEEP", "TRUE") platform.toolchain.attr_translate["DONT_TOUCH"] = ("DONT_TOUCH", "TRUE") import binascii self.address = CSRStorage(8, name="address", description="address for ROM") self.data = CSRStatus(32, name="data", description="data from ROM") rng = SystemRandom() with open("rom.db", "w") as f: for bit in range(0,32): lutsel = Signal(4) for lut in range(4): if lut == 0: lutname = 'A' elif lut == 1: lutname = 'B' elif lut == 2: lutname = 'C' else: lutname = 'D' romval = rng.getrandbits(64) # print("rom bit ", str(bit), lutname, ": ", binascii.hexlify(romval.to_bytes(8, byteorder='big'))) rom_name = "KEYROM" + str(bit) + lutname # X36Y99 and counting down if bit % 2 == 0: platform.toolchain.attr_translate[rom_name] = ("LOC", "SLICE_X36Y" + str(50 + bit // 2)) else: platform.toolchain.attr_translate[rom_name] = ("LOC", "SLICE_X37Y" + str(50 + bit // 2)) platform.toolchain.attr_translate[rom_name + 'BEL'] = ("BEL", lutname + '6LUT') platform.toolchain.attr_translate[rom_name + 'LOCK'] = ( "LOCK_PINS", "I5:A6, I4:A5, I3:A4, I2:A3, I1:A2, I0:A1" ) self.specials += [ Instance( "LUT6", name=rom_name, # p_INIT=0x0000000000000000000000000000000000000000000000000000000000000000, p_INIT=romval, i_I0= self.address.storage[0], i_I1= self.address.storage[1], i_I2= self.address.storage[2], i_I3= self.address.storage[3], i_I4= self.address.storage[4], i_I5= self.address.storage[5], o_O= lutsel[lut], attr=("KEEP", "DONT_TOUCH", rom_name, rom_name + 'BEL', rom_name + 'LOCK') ) ] # record the ROM LUT locations in a DB and annotate the initial random value given f.write("KEYROM " + str(bit) + ' ' + lutname + ' ' + platform.toolchain.attr_translate[rom_name][1] + ' ' + str(binascii.hexlify(romval.to_bytes(8, byteorder='big'))) + '\n') self.comb += [ If( self.address.storage[6:] == 0, self.data.status[bit].eq(lutsel[2])) .Elif(self.address.storage[6:] == 1, self.data.status[bit].eq(lutsel[3])) .Elif(self.address.storage[6:] == 2, self.data.status[bit].eq(lutsel[0])) .Else(self.data.status[bit].eq(lutsel[1])) ]
def __init__(self, pin): self.ctrl = CSRStorage( 8, description="Write ``0xac`` here to reboot the device") self.intro = ModuleDoc(title="Reboot Control", body=""" Access reboot control for the badge. Write the key to ``reboot`` in order to initiate a reboot.""") self.comb += If(self.ctrl.storage != 0xac, pin.eq(1))
def __init__(self, btn_pin): # Documentation self.intro = ModuleDoc("""iCEBreaker Button control. The three momentary switches on the breakaway PMOD. """) # HDL Implementation self._in = CSRStatus(len(btn_pin)) self.specials += MultiReg(btn_pin, self._in.status)
def __init__(self): self.submodules.fifo = f = fifo.SyncFIFOBuffered(width=8, depth=64) in_reg = CSRStorage(8, name="in", description=""" Write half of the FIFO to send data out the Messible. Writing to this register advances the write pointer automatically.""" ) out_reg = CSRStatus(8, name="out", description=""" Read half of the FIFO to receive data on the Messible. Reading from this register advances the read pointer automatically.""" ) self.__setattr__("in", in_reg) self.__setattr__("out", out_reg) self.status = status = CSRStatus(fields=[ CSRField( "full", description="``0`` if more data can fit into the IN FIFO."), CSRField( "have", description="``1`` if data can be read from the OUT FIFO."), ]) self.intro = ModuleDoc(""" Messible: An Ansible for Messages An Ansible is a system for instant communication across vast distances, from a small portable device to a huge terminal far away. A Messible is a message- passing system from embedded devices to a host system. You can use it to get very simple printf()-style support over a debug channel. The Messible assumes the host has a way to peek into the device's memory space. This is the case with the Wishbone bridge, which allows both the device and the host to access the same memory. At its core, a Messible is a FIFO. As long as the ``STATUS.FULL`` bit is ``0``, the device can write data into the Messible by writing into the ``IN``. However, if this value is ``1``, you need to decide if you want to wait for it to empty (if the other side is just slow), or if you want to drop the message. From the host side, you need to read ``STATUS.HAVE`` to see if there is data in the FIFO. If there is, read ``OUT`` to get the most recent byte, which automatically advances the ``READ`` pointer. """) self.comb += [ f.din.eq(in_reg.storage), f.we.eq(in_reg.re), out_reg.status.eq(f.dout), f.re.eq(out_reg.we), status.fields.full.eq(~f.writable), status.fields.have.eq(f.readable), ]
def __init__(self, reproduceable=False): self.intro = ModuleDoc("""Place and route seed. Set to a fixed number for reproduceable builds. Use a random number or your own number if you are paranoid about hardware implants that target fixed locations within the FPGA.""") if reproduceable: seed_reset = "4" # chosen by fair dice roll. guaranteed to be random. else: rng = SystemRandom() seed_reset = rng.getrandbits(64) self.seed = CSRStatus(64, name="seed", description="Seed used for the build", reset=seed_reset)
def __init__(self, led_pin, led_polarity=0x00, led_name=[]): # Documentation self.intro = ModuleDoc("""iCEBreaker LED control. The LEDs are inverted as these are negative logic LED. This means that if you set the corresponding LED bit to 1 the LED will be off and if you set it to 0 the LED will be on. """) # HDL Implementation self._out = CSRStorage( len(led_pin), fields=[CSRField(fld[0], description=fld[1]) for fld in led_name]) self.comb += led_pin.eq(self._out.storage ^ led_polarity)
def __init__(self, dram_port, pattern_mem, *, rowbits, row_shift): super().__init__(pattern_mem) self.doc = ModuleDoc(""" DMA DRAM writer. Allows to fill DRAM with a predefined pattern using DMA. Pattern ------- {common} """.format(common=BISTModule.__doc__)) dma = LiteDRAMDMAWriter(dram_port, fifo_depth=4) self.submodules += dma cmd_counter = Signal(32) self.comb += [ self.done.eq(cmd_counter), # pattern self.data_port.adr.eq(cmd_counter & self.data_mask), self.addr_port.adr.eq(cmd_counter & self.data_mask), # DMA dma.sink.address.eq(self.addr_port.dat_r + (cmd_counter & self.mem_mask)), ] # DMA data may be inverted using AddressSelector self.submodules.inverter = RowDataInverter( addr=dma.sink.address, data_in=self.data_port.dat_r, data_out=dma.sink.data, rowbits=rowbits, row_shift=row_shift, ) self.submodules.fsm = fsm = FSM() fsm.act("READY", self.ready.eq(1), If( self.start, NextValue(cmd_counter, 0), NextState("WAIT"), )) fsm.act( "WAIT", # TODO: we could pipeline the access If(cmd_counter >= self.count, NextState("READY")).Else(NextState("RUN"))) fsm.act( "RUN", dma.sink.valid.eq(1), If(dma.sink.ready, NextValue(cmd_counter, cmd_counter + 1), NextState("WAIT")))
def __init__(self, spi_pads): # Documentation self.intro = ModuleDoc("SPI slave driver") self.wishbone = bus = wishbone.Interface() self.submodules.spi = spi = HardSPISlave(spi_pads) spi_counter = Signal(4) spi_dword_mosi = Signal(32) spi_dword_miso = Signal(32) self.sync += \ If(spi.start, spi_counter.eq(0), ).Elif(spi.byte, spi_counter.eq(spi_counter + 1), spi_dword_mosi.eq(Cat(spi_dword_mosi[8:], spi.mosi)), spi_dword_miso.eq(Cat(spi_dword_miso[8:], Signal(8))), ) address = Signal(16) address_hi = Signal(16) command = Signal(8) self.comb += spi.miso.eq(spi_dword_miso[:8]) self.submodules.fsm = fsm = FSM(reset_state="IDLE") fsm.act("IDLE", If(spi.start, NextState("COMMAND"))) fsm.act( "COMMAND", If(spi.done, NextState("IDLE")).Else( If( spi_counter == 1, NextValue(command, spi_dword_mosi[-8:]), ), If(spi_counter == 3, NextValue(address, spi_dword_mosi[-16:]), If(command == 0x03, NextState("READ"))), If( (spi_counter == 7) and command == 0x02, #NextValue(data, spi_dword_mosi), NextState("WRITE"), )), ) fsm.act( "READ", bus.cyc.eq(1), bus.stb.eq(1), bus.we.eq(0), bus.adr.eq(Cat(address, address_hi)), bus.sel.eq(2**len(bus.sel) - 1), If(bus.ack, NextValue(spi_dword_miso, bus.dat_r), NextState("IDLE"))) fsm.act("WRITE", bus.cyc.eq(1), bus.stb.eq(1), bus.we.eq(1), bus.adr.eq(Cat(address, address_hi)), bus.dat_w.eq(spi_dword_mosi), bus.sel.eq(2**len(bus.sel) - 1), If(bus.ack, NextState("IDLE")))
def __init__(self, segment_pin, segment_name=[]): # Documentation self.intro = ModuleDoc("""iCEBreaker seven_segment_PMOD1A control. """) # HDL Implementation self._out = CSRStorage(len(segment_pin), fields=[ CSRField(fld[0], description=fld[1]) for fld in segment_name ]) self.comb += segment_pin.eq(self._out.storage)
def __init__(self, cntr_size, cntr_out): # Documentation self.intro = ModuleDoc("""Counter module. The CSR value determines the speed of the counter. """) # HDL Implementation self._out = CSRStorage(cntr_size) self.counter = Signal(30) self.sync.vga += self.counter.eq(self.counter + self._out.storage) self.comb += cntr_out.eq(self.counter[len(self.counter) - len(cntr_out):])
def __init__(self, dram_port, pattern_mem): super().__init__(pattern_mem) self.doc = ModuleDoc(""" DMA DRAM writer. Allows to fill DRAM with a predefined pattern using DMA. Pattern ------- {common} """.format(common=BISTModule.__doc__)) # FIXME: Increase fifo depth dma = LiteDRAMDMAWriter(dram_port, fifo_depth=1) self.submodules += dma cmd_counter = Signal(32) self.comb += [ self.done.eq(cmd_counter), # pattern self.data_port.adr.eq(cmd_counter & self.data_mask), self.addr_port.adr.eq(cmd_counter & self.data_mask), # DMA dma.sink.address.eq(self.addr_port.dat_r + (cmd_counter & self.mem_mask)), dma.sink.data.eq(self.data_port.dat_r), ] self.submodules.fsm = fsm = FSM() fsm.act("READY", self.ready.eq(1), If( self.start, NextValue(cmd_counter, 0), NextState("WAIT"), )) fsm.act( "WAIT", # TODO: we could pipeline the access If(cmd_counter >= self.count, NextState("READY")).Else(NextState("RUN"))) fsm.act( "RUN", dma.sink.valid.eq(1), If(dma.sink.ready, NextValue(cmd_counter, cmd_counter + 1), NextState("WAIT")))
def __init__(self, clkspertick): self.clkspertick = int(clkspertick) self.intro = ModuleDoc("""TickTimer: A practical systick timer. TIMER0 in the system gives a high-resolution, sysclk-speed timer which overflows very quickly and requires OS overhead to convert it into a practically usable time source which counts off in systicks, instead of sysclks. The hardware parameter to the block is the number of sysclocks per tick. """) prescaler = Signal(max=self.clkspertick, reset=self.clkspertick) timer = Signal(48) # offer up to 48 bits of system time self.control = CSRStorage( 2, fields=[ CSRField("reset", description= "Write a `1` to this bit to reset the count to 0", pulse=True), CSRField( "pause", description= "Write a `1` to this field to pause counting, 0 for free-run" ) ]) self.time = CSRStatus(48, name="time", description="""Elapsed time in systicks""") self.sync += [ If( self.control.fields.reset, timer.eq(0), prescaler.eq(self.clkspertick), ).Else( If(prescaler == 0, prescaler.eq(self.clkspertick), If( self.control.fields.pause == 0, timer.eq(timer + 1), )).Else(prescaler.eq(prescaler - 1), )) ] self.comb += self.time.status.eq(timer)
def __init__(self, pads): self.intro = ModuleDoc("""BtGpio - GPIO interface for betrusted""") gpio_in = Signal(pads.nbits) gpio_out = Signal(pads.nbits) gpio_oe = Signal(pads.nbits) for g in range(0, pads.nbits): gpio_ts = TSTriple(1) self.specials += gpio_ts.get_tristate(pads[g]) self.comb += [ gpio_ts.oe.eq(gpio_oe[g]), gpio_ts.o.eq(gpio_out[g]), gpio_in[g].eq(gpio_ts.i), ] self.output = CSRStorage(pads.nbits, name="output", description="Values to appear on GPIO when respective `drive` bit is asserted") self.input = CSRStatus(pads.nbits, name="input", description="Value measured on the respective GPIO pin") self.drive = CSRStorage(pads.nbits, name="drive", description="When a bit is set to `1`, the respective pad drives its value out") self.intena = CSRStatus(pads.nbits, name="intena", description="Enable interrupts when a respective bit is set") self.intpol = CSRStatus(pads.nbits, name="intpol", description="When a bit is `1`, falling-edges cause interrupts. Otherwise, rising edges cause interrupts.") self.specials += MultiReg(gpio_in, self.input.status) self.comb += [ gpio_out.eq(self.output.storage), gpio_oe.eq(self.drive.storage), ] self.submodules.ev = EventManager() for i in range(0, pads.nbits): setattr(self.ev, "gpioint" + str(i), EventSourcePulse() ) # pulse => rising edge self.ev.finalize() for i in range(0, pads.nbits): # pull from input.status because it's after the MultiReg synchronizer self.comb += getattr(self.ev, "gpioint" + str(i)).trigger.eq(self.input.status[i] ^ self.intpol.status[i])
def __init__(self, pads): self.intro = ModuleDoc("""BtPower - power control pins """) self.power = CSRStorage(8, fields=[ CSRField("audio", size=1, description="Write `1` to power on the audio subsystem"), CSRField("self", size=1, description="Writing `1` forces self power-on (overrides the EC's ability to power me down)", reset=1), CSRField("ec_snoop", size=1, description="Writing `1` allows the insecure EC to snoop a couple keyboard pads for wakeup key sequence recognition"), CSRField("state", size=2, description="Current SoC power state. 0x=off or not ready, 10=on and safe to shutdown, 11=on and not safe to shut down, resets to 01 to allow extSRAM access immediately during init", reset=1), CSRField("noisebias", size=1, description="Writing `1` enables the primary bias supply for the noise generator"), CSRField("noise", size=2, description="Controls which of two noise channels are active; all combos valid. noisebias must be on first.") ]) self.comb += [ pads.audio_on.eq(self.power.fields.audio), pads.fpga_sys_on.eq(self.power.fields.self), # This signal automatically enables snoop when SoC is powered down pads.allow_up5k_n.eq(~self.power.fields.ec_snoop), # Ensure SRAM isolation during reset (CE & ZZ = 1 by pull-ups) pads.pwr_s0.eq(self.power.fields.state[0] & ~ResetSignal()), # pads.pwr_s1.eq(self.power.fields.state[1]), # PATCH # pads.noisebias_on.eq(self.power.fields.noisebias), # PATCH pads.noise_on.eq(self.power.fields.noise), ]
def __init__(self, pads): self.intro = ModuleDoc("""Fomu Touchpads Fomu has four single-ended exposed pads on its side. These pads are designed to be connected to some captouch block, or driven in a resistive touch mode in order to get simple touchpad support. This block simply provides CPU-controlled GPIO support for this block. It has three registers which control the In, Out, and Output Enable functionality of each of these pins. """) touch1 = TSTriple() touch2 = TSTriple() touch3 = TSTriple() touch4 = TSTriple() self.specials += touch1.get_tristate(pads.t1) self.specials += touch2.get_tristate(pads.t2) self.specials += touch3.get_tristate(pads.t3) self.specials += touch4.get_tristate(pads.t4) self.o = CSRStorage(4, description="Output values for pads 1-4") self.oe = CSRStorage(4, description="Output enable control for pads 1-4") self.i = CSRStatus(4, description="Input value for pads 1-4") self.comb += [ touch1.o.eq(self.o.storage[0]), touch2.o.eq(self.o.storage[1]), touch3.o.eq(self.o.storage[2]), touch4.o.eq(self.o.storage[3]), touch1.oe.eq(self.oe.storage[0]), touch2.oe.eq(self.oe.storage[1]), touch3.oe.eq(self.oe.storage[2]), touch4.oe.eq(self.oe.storage[3]), self.i.status.eq(Cat(touch1.i, touch2.i, touch3.i, touch4.i)) ]
def __init__(self, pads): self.intro = ModuleDoc("""Simple soft SPI Peripheral module optimized for Betrusted-EC (UP5K arch) use Assumes a free-running sclk and csn performs the function of framing bits Thus csn must go high between each frame, you cannot hold csn low for burst transfers """) self.cipo = pads.cipo self.copi = pads.copi self.sclk = pads.sclk self.csn = pads.csn ### FIXME: stand-in for SPI clock input self.clock_domains.cd_spi_peripheral = ClockDomain() self.comb += self.cd_spi_peripheral.clk.eq(self.sclk) self.tx = CSRStorage(16, name="tx", description="""Tx data, to CIPO""") self.rx = CSRStatus(16, name="rx", description="""Rx data, from COPI""") self.control = CSRStorage(fields=[ CSRField("intena", description="Enable interrupt on transaction finished"), CSRField("clrerr", description="Clear Rx overrun error", pulse=True), ]) self.status = CSRStatus(fields=[ CSRField("tip", description="Set when transaction is in progress"), CSRField("rxfull", description="Set when Rx register has new, valid contents to read"), CSRField("rxover", description="Set if Rx register was not read before another transaction was started") ]) self.submodules.ev = EventManager() self.ev.spi_int = EventSourceProcess() # Falling edge triggered self.ev.finalize() self.comb += self.ev.spi_int.trigger.eq(self.control.fields.intena & self.status.fields.tip) # Replicate CSR into "spi" clock domain self.txrx = Signal(16) self.tip_r = Signal() self.rxfull_r = Signal() self.rxover_r = Signal() self.csn_r = Signal() self.specials += MultiReg(self.tip_r, self.status.fields.tip) self.comb += self.tip_r.eq(~self.csn) tip_d = Signal() donepulse = Signal() self.sync += tip_d.eq(self.tip_r) self.comb += donepulse.eq(~self.tip_r & tip_d) # Done pulse goes high when tip drops self.comb += self.status.fields.rxfull.eq(self.rxfull_r) self.comb += self.status.fields.rxover.eq(self.rxover_r) self.sync += [ If(self.rx.we, self.rxfull_r.eq(0), ).Else( If(donepulse, self.rxfull_r.eq(1) ).Else( self.rxfull_r.eq(self.rxfull_r), ), If(self.tip_r & self.rxfull_r, self.rxover_r.eq(1) ).Elif(self.control.fields.clrerr, self.rxover_r.eq(0) ).Else( self.rxover_r.eq(self.rxover_r) ) ) ] self.comb += self.cipo.eq(self.txrx[15]) csn_d = Signal() self.sync.spi_peripheral += [ csn_d.eq(self.csn), # "Sloppy" clock boundary crossing allowed because "rxfull" is synchronized and CPU should grab data based on that If(self.csn == 0, self.txrx.eq(Cat(self.copi, self.txrx[0:15])), self.rx.status.eq(self.rx.status), ).Else( If(self.csn & ~csn_d, self.rx.status.eq(self.txrx), ).Else( self.rx.status.eq(self.rx.status) ), self.txrx.eq(self.tx.storage) ) ]
def __init__(self, pads, pipeline_cipo=False): self.intro = ModuleDoc("""Simple soft SPI Controller module optimized for Betrusted applications Requires a clock domain 'spi', which runs at the speed of the SPI bus. Simulation benchmarks 16.5us to transfer 16x16 bit words including setup overhead (sysclk=100MHz, spiclk=25MHz) which is about 15Mbps system-level performance, assuming the receiver can keep up. A receiver running at 18MHz, with a spiclk of 20MHz, shows about 55us for 16x16bit words, or about 4.5Mbps performance. The `pipeline_cipo` argument, when set, introduces an extra pipeline stage on the return path from the peripheral. This can help improve the timing closure when talking to slow devices, such as a UP5K. However, it makes the bus not standards-compliant. Thus, by default this is set to False. For this build, `pipeline_cipo` has been set to {} """.format(pipeline_cipo)) self.cipo = pads.cipo self.copi = pads.copi self.sclk = pads.sclk self.csn = pads.csn self.hold = pads.hold hold = Signal() self.specials += MultiReg(self.hold, hold) self.tx = CSRStorage(16, name="tx", description="""Tx data, for COPI. Note: 32-bit CSRs are required for this block to work!""") self.rx = CSRStatus(16, name="rx", description="""Rx data, from CIPO""") self.control = CSRStorage(fields=[ CSRField("intena", description="Enable interrupt on transaction finished"), CSRField("autohold", description="Disallow transmission start if hold if asserted"), ]) self.status = CSRStatus(fields=[ CSRField("tip", description="Set when transaction is in progress"), CSRField("hold", description="Set when peripheral asserts hold"), ]) self.comb += self.status.fields.hold.eq(hold) self.submodules.ev = EventManager() self.ev.spi_int = EventSourceProcess() # Falling edge triggered self.ev.spi_hold = EventSourceProcess() # triggers when hold drops self.ev.finalize() self.comb += self.ev.spi_int.trigger.eq(self.control.fields.intena & self.status.fields.tip) self.comb += self.ev.spi_hold.trigger.eq(hold) # Replicate CSR into "spi" clock domain self.tx_r = Signal(16) self.rx_r = Signal(16) self.go_r = Signal() self.tx_written = Signal() setdone = Signal() done_r = Signal() self.specials += MultiReg(setdone, done_r) self.sync += [ If(self.tx.re, self.status.fields.tip.eq(1) ).Elif(done_r, self.status.fields.tip.eq(0) ).Else( self.status.fields.tip.eq(self.status.fields.tip) ) ] self.submodules.txwrite = PulseStretch() # Stretch the go signal to ensure it's picked up in the SPI domain self.comb += self.txwrite.i.eq(self.tx.re) self.comb += self.tx_written.eq(self.txwrite.o) tx_written_d = Signal() tx_go = Signal() self.sync.spi += tx_written_d.eq(self.tx_written) self.comb += tx_go.eq(~self.tx_written & tx_written_d) # Falling edge of tx_written pulse, guarantees tx.storage is stable self.csn_r = Signal(reset=1) self.comb += self.csn.eq(self.csn_r) fsm = FSM(reset_state="IDLE") fsm = ClockDomainsRenamer("spi")(fsm) self.submodules += fsm spicount = Signal(4) # I/ODDR signals clk_run = Signal() cipo_sampled = Signal() fsm.act("IDLE", NextValue(clk_run, 0), If(tx_go & ~(self.control.fields.autohold & hold), NextState("PRE_ASSERT"), NextValue(self.tx_r, self.tx.storage), # Stability guaranteed so no synchronizer necessary NextValue(spicount, 15), NextValue(self.csn_r, 0), ).Else( NextValue(self.csn_r, 1), ) ) fsm.act("PRE_ASSERT", # assert CS_N for a cycle before sending a clock, so that the receiver can bring its counter out of async clear NextState("RUN"), NextValue(clk_run, 1), ) if pipeline_cipo: turnaround_delay = 2 else: turnaround_delay = 3 fsm.act("RUN", NextValue(self.rx_r, Cat(cipo_sampled, self.rx_r[:15])), NextValue(self.tx_r, Cat(0, self.tx_r[:15])), If(spicount > 0, NextValue(clk_run, 1), NextValue(spicount, spicount - 1), ).Else( NextValue(clk_run, 0), NextValue(spicount, turnaround_delay), NextState("POST_ASSERT"), ), ) if pipeline_cipo: fsm.act("POST_ASSERT", # one cycle extra at the end, to grab the falling-edge asserted receive data NextValue(self.rx_r, Cat(cipo_sampled, self.rx_r[:15])), NextValue(self.csn_r, 1), NextState("SAMPLE"), ) fsm.act("SAMPLE", # another extra cycle to grab pipelined data NextValue(self.rx_r, Cat(cipo_sampled, self.rx_r[:15])), NextState("WAIT"), ) else: fsm.act("POST_ASSERT", # one cycle extra at the end, to grab the falling-edge asserted receive data NextValue(self.rx_r, Cat(cipo_sampled, self.rx_r[:15])), NextValue(self.csn_r, 1), NextState("WAIT"), ) fsm.act("WAIT", # guarantee a minimum CS_N high time after the transaction so Peripheral can capture. Has to perculate through multiregs, 2 cycles/ea + sync FIFO latch. NextValue(self.rx.status, self.rx_r), NextValue(spicount, spicount - 1), If(spicount == 0, setdone.eq(1), NextState("IDLE"), ) ) # generate a clock, this is Artix-specific # mirror the clock with zero delay self.specials += Instance("ODDR", p_DDR_CLK_EDGE = "SAME_EDGE", p_INIT = 0, p_SRTYPE = "SYNC", o_Q = self.sclk, i_C = ClockSignal("spi"), i_CE = 1, i_D1 = clk_run, i_D2 = 0, i_R = ResetSignal("spi"), i_S = 0, ) self.specials += Instance("ODDR", p_DDR_CLK_EDGE = "SAME_EDGE", o_Q = self.copi, i_C = ClockSignal("spi"), i_CE = 1, i_D1 = self.tx_r[15], i_D2 = self.tx_r[15], ) self.specials += Instance("IDDR", p_DDR_CLK_EDGE = "OPPOSITE_EDGE", p_SRTYPE = "SYNC", i_C = ClockSignal("spi"), i_CE = 1, i_D = self.cipo, o_Q1 = cipo_sampled, i_R = ResetSignal("spi"), i_S = 0, )
def __init__(self, pads, pipeline_cipo=False): self.intro = ModuleDoc( """SPI peripheral module optimized for Betrusted-EC (UP5K arch) use The `pipeline_cipo` argument configures the interface to put a pipeline register on the output of CIPO. Setting this to `False` gives you a standards-compliant SPI Mode 1 interface, but it should run at around 12-15MHz. Also I'm unaware of a method to convince nextpnr to fix asynchronous delays from fabric to output, and therefore the total delay from the fabric registers to the output seems to vary quite widely. Setting `pipeline_cipo` to `True` shifts the CIPO output late by a half clock cycle by resampling the CIPO line using an SB_IO register, but the clock-to-q timing is better (about 14ns versus 21ns). Also, because the origin of the data is from an SB_IO register, the timing should be very consistent regardless of nextpnr's internal machinations. The downside is that the controller needs to sample an extra bit after the SCLK stops. This isn't a problem for an FPGA design (and the matching spi_7series.SPIController IP block has a `pipeline_cipo` argument to match this block), but it won't work with most off-the-shelf microcontrollers. However, by using `pipeline_cipo`, you can increase the clock rate of the SPI bus to 20MHz with margin to spare. This module was built with `pipeline_cipo` set to {} """.format(pipeline_cipo)) self.protocol = ModuleDoc("""Enhanced Protocol for COM The large performance differential between the SoC and the EC in Precursor/Betrusted means that it's very easy for the EC to be overwhelmed with requests from the SoC. We extend the SPI protocol with a "hold" signal to inform the SoC that read data is not yet ready. This, in essence, is a matter of tying the Tx FIFO's "read empty" signal to the hold pin. The EC signals readiness to accept new commands by writing a single word to the Tx FIFO. Upon receipt of the word, the FIFO will empty, raising the "read empty" signal and producing a "hold" condition until the next data is made available for reading. """) self.copi = pads.copi self.csn = pads.csn self.hold = Signal() self.oe = Signal( ) # used to disable driving signals to the target device when it is powered down self.hold_ts = TSTriple(1) self.specials += [self.hold_ts.get_tristate(pads.hold)] self.comb += [ self.hold_ts.oe.eq(self.oe), self.hold_ts.o.eq(self.hold), ] ### clock is not wired up in this module, it's moved up to CRG for implementation-dependent buffering self.control = CSRStorage(fields=[ CSRField( "clrerr", description="Clear FIFO error flags", pulse=True), CSRField("host_int", description="0->1 raises an interrupt to the COM host" ), # rising edge triggered on other side CSRField("reset", description="Reset the fifos", pulse=True), ]) self.comb += pads.irq.eq(self.control.fields.host_int) self.status = CSRStatus(fields=[ CSRField("tip", description="Set when transaction is in progress"), CSRField( "rx_avail", description="Set when Rx FIFO has new, valid contents to read" ), CSRField("rx_over", description="Set if Rx FIFO has overflowed"), CSRField("rx_under", description="Set if Rx FIFO underflows"), CSRField("rx_level", size=12, description="Level of Rx FIFO"), CSRField("tx_avail", description="Set when Tx FIFO has space for new content"), CSRField("tx_empty", description="Set when Tx FIFO is empty"), CSRField("tx_level", size=12, description="Level of Tx FIFO"), CSRField("tx_over", description="Set when Tx FIFO overflows"), CSRField("tx_under", description="Set when Tx FIFO underflows"), ]) self.bus = bus = wishbone.Interface() rd_ack = Signal() wr_ack = Signal() self.comb += [ If( bus.we, bus.ack.eq(wr_ack), ).Else(bus.ack.eq(rd_ack), ) ] # read/rx subsystem self.submodules.rx_fifo = rx_fifo = ResetInserter(["sys"])( SyncFIFOBuffered(16, 1280) ) # should infer SB_RAM256x16's. 2560 depth > 2312 bytes = wifi MTU self.comb += self.rx_fifo.reset_sys.eq(self.control.fields.reset | ResetSignal()) self.submodules.rx_under = StickyBit() self.comb += [ self.status.fields.rx_level.eq(rx_fifo.level), self.status.fields.rx_avail.eq(rx_fifo.readable), self.rx_under.clear.eq(self.control.fields.clrerr), self.status.fields.rx_under.eq(self.rx_under.bit), ] bus_read = Signal() bus_read_d = Signal() rd_ack_pipe = Signal() self.comb += bus_read.eq(bus.cyc & bus.stb & ~bus.we & (bus.cti == 0)) self.sync += [ # This is the bus responder -- only works for uncached memory regions bus_read_d.eq(bus_read), If( bus_read & ~bus_read_d, # One response, one cycle rd_ack_pipe.eq(1), If( rx_fifo.readable, bus.dat_r.eq(rx_fifo.dout), rx_fifo.re.eq(1), self.rx_under.flag.eq(0), ).Else( # Don't stall the bus indefinitely if we try to read from an empty fifo...just # return garbage bus.dat_r.eq(0xdeadbeef), rx_fifo.re.eq(0), self.rx_under.flag.eq(1), )).Else( rx_fifo.re.eq(0), rd_ack_pipe.eq(0), self.rx_under.flag.eq(0), ), rd_ack.eq(rd_ack_pipe), ] # tx/write spiperipheral self.submodules.tx_fifo = tx_fifo = ResetInserter(["sys"])( SyncFIFOBuffered(16, 1280)) self.comb += self.tx_fifo.reset_sys.eq(self.control.fields.reset | ResetSignal()) self.submodules.tx_over = StickyBit() self.comb += [ self.tx_over.clear.eq(self.control.fields.clrerr), self.status.fields.tx_over.eq(self.tx_over.bit), self.status.fields.tx_empty.eq(~tx_fifo.readable), self.status.fields.tx_avail.eq(tx_fifo.writable), self.status.fields.tx_level.eq(tx_fifo.level), ] write_gate = Signal() self.sync += [ If( bus.cyc & bus.stb & bus.we & ~bus.ack, If( tx_fifo.writable, tx_fifo.din.eq(bus.dat_w), tx_fifo.we.eq( ~write_gate), # ensure write is just single cycle wr_ack.eq(1), write_gate.eq(1), ).Else( self.tx_over.flag.eq(1), tx_fifo.we.eq(0), wr_ack.eq(0), write_gate.eq(0), )).Else( write_gate.eq(0), tx_fifo.we.eq(0), wr_ack.eq(0), ) ] # Replica CSR into "spi" clock domain self.tx = Signal(16, reset_less=True) self.tip_r = Signal() self.rxfull_r = Signal() self.rxover_r = Signal() self.csn_r = Signal() self.specials += MultiReg(~self.csn, self.tip_r) self.comb += self.status.fields.tip.eq(self.tip_r) tip_d = Signal() donepulse = Signal() self.sync += tip_d.eq(self.tip_r) self.comb += donepulse.eq( ~self.tip_r & tip_d) # done pulse goes high when tip drops self.submodules.rx_over = StickyBit() self.comb += [ self.status.fields.rx_over.eq(self.rx_over.bit), self.rx_over.clear.eq(self.control.fields.clrerr), self.rx_over.flag.eq(~self.rx_fifo.writable & donepulse), ] self.submodules.tx_under = StickyBit() self.comb += [ self.status.fields.tx_under.eq(self.tx_under.bit), self.tx_under.clear.eq(self.control.fields.clrerr), self.tx_under.flag.eq(~self.tx_fifo.readable & donepulse), ] rx = Signal(16, reset_less=True) self.comb += [ self.rx_fifo.din.eq( rx ), # assume CS is high for quite a while before donepulse triggers the write, this stabilizes the rx din self.rx_fifo.we.eq(donepulse), self.tx_fifo.re.eq(donepulse), ] # form the SPI-clock domain shift registers. self.comb += [ self.hold.eq(~self.tx_fifo.readable), ] # input register on copi. Falling edge sampling. self.specials += Instance( "SB_IO", p_IO_STANDARD="SB_LVCMOS", p_PIN_TYPE=0b0000_00, p_NEG_TRIGGER=1, io_PACKAGE_PIN=self.copi, i_CLOCK_ENABLE=1, i_INPUT_CLK=ClockSignal("sclk"), i_OUTPUT_ENABLE=0, o_D_IN_0=rx[0], # D_IN_0 is falling edge when NEG_TRIGGER is 1 ) for bit in range(15): self.specials += Instance( "SB_DFFN", i_C=ClockSignal("sclk"), i_D=rx[bit], o_Q=rx[bit + 1], ) if pipeline_cipo: # output register on cipo. produces new result on falling-edge # this improves Tc-q timing, and more importantly, keeps it consistent between builds self.specials += Instance( "SB_IO", p_IO_STANDARD="SB_LVCMOS", p_PIN_TYPE=0b1001_00, p_NEG_TRIGGER= 1, # this causes the output to update on the falling edge io_PACKAGE_PIN=pads.cipo, i_CLOCK_ENABLE=1, i_OUTPUT_CLK=ClockSignal("sclk"), i_OUTPUT_ENABLE=self.oe, i_D_OUT_0=self.tx[15], ) # tx is updated on the rising edge, but the SB_IO primitive pushes the new data out on the falling edge # so the total time we have to move the data from this shift register to the output register is a half # clock cycle. spi_load = Signal() self.sync.sclk += [ If( spi_load, If( self.tx_fifo.readable, self.tx.eq(self.tx_fifo.dout), ).Else( self.tx.eq( 0xDDDD), # in case of underflow send error code ), ).Else( self.tx.eq(Cat(1, self.tx[0:15]) ) # if we overshift, we eventually get all 1's ) ] self.specials += Instance( "SB_DFFS", i_C=ClockSignal("sclk"), i_D=0, o_Q=spi_load, i_S=self.csn, ) else: # this path gets you a "standards compliant" SPI interface, but it's slower and the timing is less reliable self.cipo_ts = TSTriple(1) self.specials += [self.cipo_ts.get_tristate(pads.cipo)] self.comb += [ self.cipo_ts.oe.eq(self.oe), self.cipo_ts.o.eq(self.tx[15]), ] tx_staged = Signal(16) for bit in range(16): if bit != 0: self.specials += Instance( "SB_DFFS", i_C=ClockSignal("sclk"), i_D=self.tx[bit - 1], o_Q=self.tx[bit], i_S=tx_staged[bit] & self.csn, ) else: self.specials += Instance( "SB_DFFS", i_C=ClockSignal("sclk"), i_D=0, o_Q=self.tx[bit], i_S=tx_staged[bit] & self.csn, ) self.comb += [ If( self.tx_fifo.readable, tx_staged.eq(self.tx_fifo.dout), ).Else(tx_staged.eq(0xDDDD), ) ]
def __init__(self, pads, fifo_depth=256): self.intro = ModuleDoc("""Intro I2S slave creates a slave audio interface instance. Tx and Rx interfaces are inferred based upon the presence or absence of the respective pins in the "pads" argument. The interface is I2S-like, but note the deviation that the bits are justified left without a 1-bit pad after sync edges. This isn't a problem for talking to the LM49352 codec this was designed for, as the bit offset is programmable, but this will not work well if are talking to a CODEC without a programmable bit offset! System Interface ---------------- Audio interchange is done with the system using 16-bit stereo samples, with the right channel mapped to the least significant word of a 32-bit word. Thus each 32-bit word is a single stereo sample. As this is a slave I2S interface, sampling rate and framing is set by the programming of the audio CODEC chip. A slave situation is preferred because this defers the generation of audio clocks to the CODEC, which has PLLs specialized to generate the correct frequencies for audio sampling rates. `fifo_depth` is the depth at which either a read interrupt is fired (guaranteeing at least `fifo_depth` stereo samples in the receive FIFO) or a write interrupt is fired (guaranteeing at least `fifo_depth` free space in the transmit FIFO). The maximum depth is 512. To receive audio data: - reset the Rx FIFO, to guarantee all pointers at zero - hook the Rx full interrupt with an interrupt handler (optional) - if the CODEC is not yet transmitting data, initiate data transmission - enable Rx FIFO to run - poll or wait for interrupt; upon interrupt, read `fifo_depth` words. Repeat. - to close the stream, simply clear the Rx FIFO enable bit. The next initiation should call a reset of the FIFO to ensure leftover previous data is cleared from the FIFO. To transmit audio data: - reset the Tx FIFO, to guarantee all pointers at zero - hook the Tx available interrupt with an interrupt handler (optional) - write 512 words of data into the Tx FIFO, filling it to the max - if the CODEC is not yet requesting data and unmuted, unmute and initiate reception - enable the Tx FIFO to run - poll or wait for interrupt; upon interrupt, write `fifo_depth` words. Repeat. - to close stream, mute the DAC and stop the request clock. Ideally, this can be completed before the FIFO is emptied, so there is no jarring pop or truncation of data - stop FIFO running. Next initiation should reset the FIFO to ensure leftover previous data in FIFO is cleared. CODEC Interface --------------- The interface assumes we have a sysclk domain running around 100MHz, and that our typical max audio rate is 44.1kHz * 24bits * 2channels = 2.1168MHz audio clock. Thus, the architecture treats the audio clock and data as asynchronous inputs that are MultiReg-syncd into the clock domain. Probably the slowest sysclk rate this might work with is around 20-25MHz (10x over sampling), but at 100MHz things will be quite comfortable. The upside of the fully asynchronous implementation is that we can leave the I/O unconstrained, giving the place/route more latitude to do its job. Here's the timing format targeted by this I2S interface: .. wavedrom:: :caption: Timing format of the I2S interface { "signal" : [ { "name": "clk", "wave": "n....|.......|......" }, { "name": "sync", "wave": "1.0..|....1..|....0." }, { "name": "tx/rx", "wave": ".====|==x.===|==x.=x", "data": ["L15", "L14", "...", "L1", "L0", "R15", "R14", "...", "R1", "R0", "L15"] }, ]} - Data is updated on the falling edge - Data is sampled on the rising edge - Words are MSB-to-LSB, left-justified (**NOTE: this is a deviation from strict I2S, which offsets by 1 from the left**) - Sync is an input (FPGA is slave, codec is master): low => left channel, high => right channel - Sync can be longer than the wordlen, extra bits are just ignored - Tx is data to the codec (SDI pin on LM49352) - Rx is data from the codec (SDO pin on LM49352) """) # One cache line is 8 32-bit words, need to always have enough space for one line or else # nothing works if fifo_depth > 504: fifo_depth = 504 print( "I2S warning: fifo depth greater than 504 selected; truncating to 504" ) if fifo_depth < 8: fifo_depth = 8 print( "I2S warning: fifo depth less than 8 selected; truncating to 8" ) # Connect pins, synchronizers, and edge detectors if hasattr(pads, 'tx'): tx_pin = Signal() self.comb += pads.tx.eq(tx_pin) if hasattr(pads, 'rx'): rx_pin = Signal() self.specials += MultiReg(pads.rx, rx_pin) sync_pin = Signal() self.specials += MultiReg(pads.sync, sync_pin) clk_pin = Signal() self.specials += MultiReg(pads.clk, clk_pin) clk_d = Signal() self.sync += clk_d.eq(clk_pin) rising_edge = Signal() falling_edge = Signal() self.comb += [ rising_edge.eq(clk_pin & ~clk_d), falling_edge.eq(~clk_pin & clk_d) ] # Wishbone bus self.bus = bus = wishbone.Interface() rd_ack = Signal() wr_ack = Signal() self.comb += [ If( bus.we, bus.ack.eq(wr_ack), ).Else(bus.ack.eq(rd_ack), ) ] # Interrupts self.submodules.ev = EventManager() if hasattr(pads, 'rx'): self.ev.rx_ready = EventSourcePulse( description="Indicates FIFO is ready to read" ) # Rising edge triggered self.ev.rx_error = EventSourcePulse( description= "Indicates an Rx error has happened (over/underflow)") if hasattr(pads, 'tx'): self.ev.tx_ready = EventSourcePulse( description= "Indicates enough space available for next Tx quanta of {} words" .format(fifo_depth)) self.ev.tx_error = EventSourcePulse( description="Indicates a Tx error has happened (over/underflow" ) self.ev.finalize() # build the RX subsystem if hasattr(pads, 'rx'): rx_rd_d = Signal(32) rx_almostfull = Signal() rx_almostempty = Signal() rx_full = Signal() rx_empty = Signal() rx_rdcount = Signal(9) rx_rderr = Signal() rx_wrerr = Signal() rx_wrcount = Signal(9) rx_rden = Signal() rx_wr_d = Signal(32) rx_wren = Signal() self.rx_ctl = CSRStorage( description="Rx data path control", fields=[ CSRField("enable", size=1, description="Enable the receiving data"), CSRField( "reset", size=1, description= "Writing `1` resets the FIFO. Reset happens regardless of enable state.", pulse=1) ]) self.rx_stat = CSRStatus( description="Rx data path status", fields=[ CSRField("overflow", size=1, description="Rx overflow"), CSRField("underflow", size=1, description="Rx underflow"), CSRField( "dataready", size=1, description="{} words of data loaded and ready to read" .format(fifo_depth)), CSRField("empty", size=1, description="No data available in FIFO to read" ), # next flags probably never used CSRField("wrcount", size=9, description="Write count"), CSRField("rdcount", size=9, description="Read count"), CSRField("fifo_depth", size=9, description="FIFO depth as synthesized") ]) self.comb += self.rx_stat.fields.fifo_depth.eq(fifo_depth) rx_rst_cnt = Signal(3) rx_reset = Signal() self.sync += [ If( self.rx_ctl.fields.reset, rx_rst_cnt.eq(5), # 5 cycles reset required by design rx_reset.eq(1)).Else( If(rx_rst_cnt == 0, rx_reset.eq(0)).Else(rx_rst_cnt.eq(rx_rst_cnt - 1), rx_reset.eq(1))) ] # At a width of 32 bits, an 18kiB fifo is 512 entries deep self.specials += Instance( "FIFO_SYNC_MACRO", p_DEVICE="7SERIES", p_FIFO_SIZE="18Kb", p_DATA_WIDTH=32, p_ALMOST_EMPTY_OFFSET=8, p_ALMOST_FULL_OFFSET=(512 - fifo_depth), p_DO_REG=0, i_CLK=ClockSignal(), i_RST=rx_reset, o_ALMOSTFULL=rx_almostfull, o_ALMOSTEMPTY=rx_almostempty, o_FULL=rx_full, o_EMPTY=rx_empty, i_WREN=rx_wren & ~rx_reset, i_DI=rx_wr_d, i_RDEN=rx_rden & ~rx_reset, o_DO=rx_rd_d, o_RDCOUNT=rx_rdcount, o_RDERR=rx_rderr, o_WRCOUNT=rx_wrcount, o_WRERR=rx_wrerr, ) self.comb += [ # Wire up the status signals and interrupts self.rx_stat.fields.overflow.eq(rx_wrerr), self.rx_stat.fields.underflow.eq(rx_rderr), self.rx_stat.fields.dataready.eq(rx_almostfull), self.rx_stat.fields.wrcount.eq(rx_wrcount), self.rx_stat.fields.rdcount.eq(rx_rdcount), self.ev.rx_ready.trigger.eq(rx_almostfull), self.ev.rx_error.trigger.eq(rx_wrerr | rx_rderr), ] bus_read = Signal() bus_read_d = Signal() rd_ack_pipe = Signal() self.comb += bus_read.eq(bus.cyc & bus.stb & ~bus.we & (bus.cti == 0)) self.sync += [ # This is the bus responder -- only works for uncached memory regions bus_read_d.eq(bus_read), If( bus_read & ~bus_read_d, # One response, one cycle rd_ack_pipe.eq(1), If( ~rx_empty, bus.dat_r.eq(rx_rd_d), rx_rden.eq(1), ).Else( # Don't stall the bus indefinitely if we try to read from an empty fifo...just # return garbage bus.dat_r.eq(0xdeadbeef), rx_rden.eq(0), )).Else( rx_rden.eq(0), rd_ack_pipe.eq(0), ), rd_ack.eq(rd_ack_pipe), ] rx_cnt = Signal(5) self.submodules.rxi2s = rxi2s = FSM(reset_state="IDLE") rxi2s.act( "IDLE", NextValue(rx_wr_d, 0), If( self.rx_ctl.fields.enable, # Wait_sync guarantees we start at the beginning of a left frame, and not in # the middle If(rising_edge & sync_pin, NextState("WAIT_SYNC")))), rxi2s.act( "WAIT_SYNC", If(rising_edge & ~sync_pin, NextState("LEFT"), NextValue(rx_cnt, 16)), ) rxi2s.act( "LEFT", If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else( NextValue(rx_wr_d, Cat(rx_pin, rx_wr_d[:-1])), NextValue(rx_cnt, rx_cnt - 1), NextState("LEFT_WAIT"))) rxi2s.act( "LEFT_WAIT", If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else( If( rising_edge, If((rx_cnt == 0) & sync_pin, NextValue(rx_cnt, 16), NextState("RIGHT")).Elif(rx_cnt > 0, NextState("LEFT"))))) rxi2s.act( "RIGHT", If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else( NextValue(rx_wr_d, Cat(rx_pin, rx_wr_d[:-1])), NextValue(rx_cnt, rx_cnt - 1), NextState("RIGHT_WAIT"))) rxi2s.act( "RIGHT_WAIT", If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else( If( rising_edge, If( (rx_cnt == 0) & ~sync_pin, NextValue(rx_cnt, 16), NextState("LEFT"), rx_wren.eq( 1 ) # Pulse rx_wren to write the current data word ).Elif(rx_cnt > 0, NextState("RIGHT"))))) # Build the TX subsystem if hasattr(pads, 'tx'): tx_rd_d = Signal(32) tx_almostfull = Signal() tx_almostempty = Signal() tx_full = Signal() tx_empty = Signal() tx_rdcount = Signal(9) tx_rderr = Signal() tx_wrerr = Signal() tx_wrcount = Signal(9) tx_rden = Signal() tx_wr_d = Signal(32) tx_wren = Signal() self.tx_ctl = CSRStorage( description="Tx data path control", fields=[ CSRField("enable", size=1, description="Enable the transmission data"), CSRField( "reset", size=1, description= "Writing `1` resets the FIFO. Reset happens regardless of enable state.", pulse=1) ]) self.tx_stat = CSRStatus( description="Tx data path status", fields=[ CSRField("overflow", size=1, description="Tx overflow"), CSRField("underflow", size=1, description="Tx underflow"), CSRField( "free", size=1, description="At least {} words of space free".format( fifo_depth)), CSRField("almostfull", size=1, description="Less than 8 words space available" ), # the next few flags should be rarely used CSRField("full", size=1, description="FIFO is full or overfull"), CSRField("empty", size=1, description="FIFO is empty"), CSRField("wrcount", size=9, description="Tx write count"), CSRField("rdcount", size=9, description="Tx read count"), ]) tx_rst_cnt = Signal(3) tx_reset = Signal() self.sync += [ If( self.tx_ctl.fields.reset, tx_rst_cnt.eq(5), # 5 cycles reset required by design tx_reset.eq(1)).Else( If(tx_rst_cnt == 0, tx_reset.eq(0)).Else(tx_rst_cnt.eq(tx_rst_cnt - 1), tx_reset.eq(1))) ] # At a width of 32 bits, an 18kiB fifo is 512 entries deep self.specials += Instance( "FIFO_SYNC_MACRO", p_DEVICE="7SERIES", p_FIFO_SIZE="18Kb", p_DATA_WIDTH=32, p_ALMOST_EMPTY_OFFSET=fifo_depth, p_ALMOST_FULL_OFFSET=8, p_DO_REG=0, i_CLK=ClockSignal(), i_RST=tx_reset, o_ALMOSTFULL=tx_almostfull, o_ALMOSTEMPTY=tx_almostempty, o_FULL=tx_full, o_EMPTY=tx_empty, i_WREN=tx_wren & ~tx_reset, i_DI=tx_wr_d, i_RDEN=tx_rden & ~tx_reset, o_DO=tx_rd_d, o_RDCOUNT=tx_rdcount, o_RDERR=tx_rderr, o_WRCOUNT=tx_wrcount, o_WRERR=tx_wrerr, ) self.comb += [ # Wire up the status signals and interrupts self.tx_stat.fields.overflow.eq(tx_wrerr), self.tx_stat.fields.underflow.eq(tx_rderr), self.tx_stat.fields.free.eq(tx_almostempty), self.tx_stat.fields.almostfull.eq(tx_almostfull), self.tx_stat.fields.full.eq(tx_full), self.tx_stat.fields.empty.eq(tx_empty), self.tx_stat.fields.rdcount.eq(tx_rdcount), self.tx_stat.fields.wrcount.eq(tx_wrcount), self.ev.tx_ready.trigger.eq(tx_almostempty), self.ev.tx_error.trigger.eq(tx_wrerr | tx_rderr), ] self.sync += [ # This is the bus responder -- need to check how this interacts with uncached memory # region If( bus.cyc & bus.stb & bus.we & ~bus.ack, If( ~tx_full, tx_wr_d.eq(bus.dat_w), tx_wren.eq(1), wr_ack.eq(1), ).Else( tx_wren.eq(0), wr_ack.eq(0), )).Else( tx_wren.eq(0), wr_ack.eq(0), ) ] tx_cnt = Signal(5) tx_buf = Signal(32) self.submodules.txi2s = txi2s = FSM(reset_state="IDLE") txi2s.act( "IDLE", If(self.tx_ctl.fields.enable, If( falling_edge & sync_pin, NextState("WAIT_SYNC"), ))), txi2s.act( "WAIT_SYNC", If(falling_edge & ~sync_pin, NextState("LEFT"), NextValue(tx_cnt, 16), NextValue(tx_buf, tx_rd_d), tx_rden.eq(1))) txi2s.act( "LEFT", If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else( NextValue(tx_pin, tx_buf[31]), NextValue(tx_buf, Cat(0, tx_buf[:-1])), NextValue(tx_cnt, tx_cnt - 1), NextState("LEFT_WAIT"))) txi2s.act( "LEFT_WAIT", If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else( If( falling_edge, If((tx_cnt == 0) & sync_pin, NextValue(tx_cnt, 16), NextState("RIGHT")).Elif(tx_cnt > 0, NextState("LEFT"))))) txi2s.act( "RIGHT", If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else( NextValue(tx_pin, tx_buf[31]), NextValue(tx_buf, Cat(0, tx_buf[:-1])), NextValue(tx_cnt, tx_cnt - 1), NextState("RIGHT_WAIT"))) txi2s.act( "RIGHT_WAIT", If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else( If( falling_edge, If((tx_cnt == 0) & ~sync_pin, NextValue(tx_cnt, 16), NextState("LEFT"), NextValue(tx_buf, tx_rd_d), tx_rden.eq(1)).Elif(tx_cnt > 0, NextState("RIGHT")))))
def __init__(self, pads, fifo_depth=256, controller=False, master=False, concatenate_channels=True, sample_width=16, frame_format=I2S_FORMAT.I2S_LEFT_JUSTIFIED, lrck_ref_freq=100e6, lrck_freq=44100, bits_per_channel=28): if master == True: print( "Master/slave terminology deprecated, please use controller/peripheral. Please see http://oshwa.org/a-resolution-to-redefine-spi-signal-names." ) controller = True self.intro = ModuleDoc("""Intro I2S controller/peripheral creates a controller/peripheral audio interface instance depending on a configured controller variable. Tx and Rx interfaces are inferred based upon the presence or absence of the respective pins in the "pads" argument. When device is configured as controller you can manipulate LRCK and SCLK signals using below variables. - lrck_ref_freq - is a reference signal that is required to achive desired LRCK and SCLK frequencies. Have be the same as your sys_clk. - lrck_freq - this variable defines requested LRCK frequency. Mind you, that based on sys_clk frequency, configured value will be more or less acurate. - bits_per_channel - defines SCLK frequency. Mind you, that based on sys_clk frequency, the requested amount of bits per channel may vary from configured. When device is configured as peripheral I2S interface, sampling rate and framing is set by the programming of the audio CODEC chip. A peripheral configuration defers the generation of audio clocks to the CODEC, which has PLLs specialized to generate the correct frequencies for audio sampling rates. I2S core supports two formats: standard and left-justified. - Standard format requires a device to receive and send data with one bit offset for both channels. Left channel begins with low signal on LRCK. - Left justified format requires from device to receive and send data without any bit offset for both channels. Left channel begins with high signal on LRCK. Sample width can be any of 1 to 32 bits. When sample_width is less than or equal to 16-bit and concatenate_channels is enabled, sending and reciving channels is performed atomically. eg. both samples are transfered from/to fifo in single operation. System Interface ---------------- `fifo_depth` is the depth at which either a read interrupt is fired (guaranteeing at least `fifo_depth` stereo samples in the receive FIFO) or a write interrupt is fired (guaranteeing at least `fifo_depth` free space in the transmit FIFO). The maximum depth is 512. To receive audio data: - reset the Rx FIFO, to guarantee all pointers at zero - hook the Rx full interrupt with an interrupt handler (optional) - if the CODEC is not yet transmitting data, initiate data transmission - enable Rx FIFO to run - poll or wait for interrupt; upon interrupt, read `fifo_depth` words. Repeat. - to close the stream, simply clear the Rx FIFO enable bit. The next initiation should call a reset of the FIFO to ensure leftover previous data is cleared from the FIFO. To transmit audio data: - reset the Tx FIFO, to guarantee all pointers at zero - hook the Tx available interrupt with an interrupt handler (optional) - write 512 words of data into the Tx FIFO, filling it to the max - if the CODEC is not yet requesting data and unmuted, unmute and initiate reception - enable the Tx FIFO to run - poll or wait for interrupt; upon interrupt, write `fifo_depth` words. Repeat. - to close stream, mute the DAC and stop the request clock. Ideally, this can be completed before the FIFO is emptied, so there is no jarring pop or truncation of data - stop FIFO running. Next initiation should reset the FIFO to ensure leftover previous data in FIFO is cleared. CODEC Interface --------------- The interface assumes we have a sysclk domain running around 100MHz, and that our typical max audio rate is 44.1kHz * 24bits * 2channels = 2.1168MHz audio clock. Thus, the architecture treats the audio clock and data as asynchronous inputs that are MultiReg-syncd into the clock domain. Probably the slowest sysclk rate this might work with is around 20-25MHz (10x over sampling), but at 100MHz things will be quite comfortable. The upside of the fully asynchronous implementation is that we can leave the I/O unconstrained, giving the place/route more latitude to do its job. Here's the timing format targeted by this I2S interface: .. wavedrom:: :caption: Timing format of the I2S interface { "signal" : [ { "name": "clk", "wave": "n....|.......|......" }, { "name": "sync", "wave": "1.0..|....1..|....0." }, { "name": "tx/rx", "wave": ".====|==x.===|==x.=x", "data": ["L15", "L14", "...", "L1", "L0", "R15", "R14", "...", "R1", "R0", "L15"] }, ]} - Data is updated on the falling edge - Data is sampled on the rising edge - Words are MSB-to-LSB, - Sync is an input or output based on configure mode, - Sync can be longer than the wordlen, extra bits are just ignored - Tx is data to the codec (SDI pin on LM49352) - Rx is data from the codec (SDO pin on LM49352) """) # One cache line is 8 32-bit words, need to always have enough space for one line or else # nothing works if fifo_depth > 504: fifo_depth = 504 print( "I2S warning: fifo depth greater than 504 selected; truncating to 504" ) if fifo_depth < 8: fifo_depth = 8 print( "I2S warning: fifo depth less than 8 selected; truncating to 8" ) if sample_width > 32: sample_width = 32 print( "I2S warning: sample width greater than 32 bits. truncating to 32" ) # Connect pins, synchronizers, and edge detectors if hasattr(pads, 'tx'): tx_pin = Signal() self.comb += pads.tx.eq(tx_pin) if hasattr(pads, 'rx'): rx_pin = Signal() self.specials += MultiReg(pads.rx, rx_pin) fifo_data_width = sample_width if concatenate_channels: if sample_width <= 16: fifo_data_width = sample_width * 2 else: concatenate_channels = False print( "I2S warning: sample width greater than 16 bits. your channels can't be glued" ) sync_pin = Signal() self.specials += MultiReg(pads.sync, sync_pin) clk_pin = Signal() self.specials += MultiReg(pads.clk, clk_pin) clk_d = Signal() self.sync += clk_d.eq(clk_pin) rising_edge = Signal() falling_edge = Signal() self.comb += [ rising_edge.eq(clk_pin & ~clk_d), falling_edge.eq(~clk_pin & clk_d) ] # Wishbone bus self.bus = bus = wishbone.Interface() rd_ack = Signal() wr_ack = Signal() self.comb += [ If( bus.we, bus.ack.eq(wr_ack), ).Else(bus.ack.eq(rd_ack), ) ] if controller == True: if bits_per_channel < sample_width and frame_format == I2S_FORMAT.I2S_STANDARD: bits_per_channel = sample_width + 1 print( "I2S warning: bits per channel can't be smaller than sample_width. Setting bits per channel to {}" .format(sample_width + 1)) # implementing LRCK signal lrck_period = int(lrck_ref_freq / (lrck_freq * 2)) lrck_counter = Signal(16) self.sync += [ If( (lrck_counter == lrck_period), lrck_counter.eq(0), pads.sync.eq(~pads.sync), ).Else(lrck_counter.eq(lrck_counter + 1)) ] # implementing SCLK signal sclk_period = int(lrck_period / (bits_per_channel * 2)) sclk_counter = Signal(16) self.sync += [ If( (sclk_counter == sclk_period), sclk_counter.eq(0), pads.clk.eq(~pads.clk), ).Else(sclk_counter.eq(sclk_counter + 1)) ] # Interrupts self.submodules.ev = EventManager() if hasattr(pads, 'rx'): self.ev.rx_ready = EventSourcePulse( description="Indicates FIFO is ready to read" ) # Rising edge triggered self.ev.rx_error = EventSourcePulse( description= "Indicates an Rx error has happened (over/underflow)") if hasattr(pads, 'tx'): self.ev.tx_ready = EventSourcePulse( description= "Indicates enough space available for next Tx quanta of {} words" .format(fifo_depth)) self.ev.tx_error = EventSourcePulse( description="Indicates a Tx error has happened (over/underflow" ) self.ev.finalize() # build the RX subsystem if hasattr(pads, 'rx'): rx_rd_d = Signal(fifo_data_width) rx_almostfull = Signal() rx_almostempty = Signal() rx_full = Signal() rx_empty = Signal() rx_rdcount = Signal(9) rx_rderr = Signal() rx_wrerr = Signal() rx_wrcount = Signal(9) rx_rden = Signal() rx_wr_d = Signal(fifo_data_width) rx_wren = Signal() self.rx_ctl = CSRStorage( description="Rx data path control", fields=[ CSRField("enable", size=1, description="Enable the receiving data"), CSRField( "reset", size=1, description= "Writing `1` resets the FIFO. Reset happens regardless of enable state.", pulse=1) ]) self.rx_stat = CSRStatus( description="Rx data path status", fields=[ CSRField("overflow", size=1, description="Rx overflow"), CSRField("underflow", size=1, description="Rx underflow"), CSRField( "dataready", size=1, description="{} words of data loaded and ready to read" .format(fifo_depth)), CSRField("empty", size=1, description="No data available in FIFO to read" ), # next flags probably never used CSRField("wrcount", size=9, description="Write count"), CSRField("rdcount", size=9, description="Read count"), CSRField("fifo_depth", size=9, description="FIFO depth as synthesized"), CSRField( "concatenate_channels", size=1, reset=concatenate_channels, description="Receive and send both channels atomically" ) ]) self.rx_conf = CSRStatus( description="Rx configuration", fields=[ CSRField( "format", size=2, reset=frame_format.value, description= "I2S sample format. {} is left-justified, {} is I2S standard" .format(I2S_FORMAT.I2S_LEFT_JUSTIFIED, I2S_FORMAT.I2S_STANDARD)), CSRField("sample_width", size=6, reset=sample_width, description="Single sample width"), CSRField("lrck_freq", size=24, reset=lrck_freq, description="Audio sampling rate frequency"), ]) self.comb += self.rx_stat.fields.fifo_depth.eq(fifo_depth) rx_rst_cnt = Signal(3) rx_reset = Signal() self.sync += [ If( self.rx_ctl.fields.reset, rx_rst_cnt.eq(5), # 5 cycles reset required by design rx_reset.eq(1)).Else( If(rx_rst_cnt == 0, rx_reset.eq(0)).Else(rx_rst_cnt.eq(rx_rst_cnt - 1), rx_reset.eq(1))) ] # At a width of 32 bits, an 18kiB fifo is 512 entries deep self.specials += Instance( "FIFO_SYNC_MACRO", p_DEVICE="7SERIES", p_FIFO_SIZE="18Kb", p_DATA_WIDTH=fifo_data_width, p_ALMOST_EMPTY_OFFSET=8, p_ALMOST_FULL_OFFSET=(512 - fifo_depth), p_DO_REG=0, i_CLK=ClockSignal(), i_RST=rx_reset, o_ALMOSTFULL=rx_almostfull, o_ALMOSTEMPTY=rx_almostempty, o_FULL=rx_full, o_EMPTY=rx_empty, i_WREN=rx_wren & ~rx_reset, i_DI=rx_wr_d, i_RDEN=rx_rden & ~rx_reset, o_DO=rx_rd_d, o_RDCOUNT=rx_rdcount, o_RDERR=rx_rderr, o_WRCOUNT=rx_wrcount, o_WRERR=rx_wrerr, ) self.comb += [ # Wire up the status signals and interrupts self.rx_stat.fields.overflow.eq(rx_wrerr), self.rx_stat.fields.underflow.eq(rx_rderr), self.rx_stat.fields.dataready.eq(rx_almostfull), self.rx_stat.fields.wrcount.eq(rx_wrcount), self.rx_stat.fields.rdcount.eq(rx_rdcount), self.rx_stat.fields.empty.eq(rx_empty), self.ev.rx_ready.trigger.eq(rx_almostfull), self.ev.rx_error.trigger.eq(rx_wrerr | rx_rderr), ] bus_read = Signal() bus_read_d = Signal() rd_ack_pipe = Signal() self.comb += bus_read.eq(bus.cyc & bus.stb & ~bus.we & (bus.cti == 0)) self.sync += [ # This is the bus responder -- only works for uncached memory regions bus_read_d.eq(bus_read), If( bus_read & ~bus_read_d, # One response, one cycle rd_ack_pipe.eq(1), If( ~rx_empty, bus.dat_r.eq(rx_rd_d), rx_rden.eq(1), ).Else( # Don't stall the bus indefinitely if we try to read from an empty fifo...just # return garbage bus.dat_r.eq(0xdeadbeef), rx_rden.eq(0), )).Else( rx_rden.eq(0), rd_ack_pipe.eq(0), ), rd_ack.eq(rd_ack_pipe), ] rx_cnt_width = math.ceil(math.log(fifo_data_width, 2)) rx_cnt = Signal(rx_cnt_width) rx_delay_cnt = Signal() rx_delay_val = 1 if frame_format == I2S_FORMAT.I2S_STANDARD else 0 self.submodules.rxi2s = rxi2s = FSM(reset_state="IDLE") rxi2s.act( "IDLE", NextValue(rx_wr_d, 0), If( self.rx_ctl.fields.enable, # Wait_sync guarantees we start at the beginning of a left frame, and not in # the middle If( rising_edge & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin), NextState("WAIT_SYNC"), NextValue(rx_delay_cnt, rx_delay_val)))), rxi2s.act( "WAIT_SYNC", If( rising_edge & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin), If(rx_delay_cnt > 0, NextValue(rx_delay_cnt, rx_delay_cnt - 1), NextState("WAIT_SYNC")).Else( NextState("LEFT"), NextValue(rx_delay_cnt, rx_delay_val), NextValue(rx_cnt, sample_width))), ) rxi2s.act( "LEFT", If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else( NextValue(rx_wr_d, Cat(rx_pin, rx_wr_d[:-1])), NextValue(rx_cnt, rx_cnt - 1), NextState("LEFT_WAIT"))) if concatenate_channels: rxi2s.act( "LEFT_WAIT", If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else( If( rising_edge, If((rx_cnt == 0), If((sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else ~sync_pin), If( rx_delay_cnt == 0, NextValue(rx_cnt, sample_width), NextValue(rx_delay_cnt, rx_delay_val), NextState("RIGHT"), ).Else( NextValue(rx_delay_cnt, rx_delay_cnt - 1), NextState("LEFT_WAIT"))).Else( NextState("LEFT_WAIT"))).Elif( rx_cnt > 0, NextState("LEFT"))))) else: rxi2s.act( "LEFT_WAIT", If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else( If( rising_edge, If( (rx_cnt == 0), If( (sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else ~sync_pin), If( rx_delay_cnt == 0, NextValue(rx_cnt, sample_width), NextValue(rx_delay_cnt, rx_delay_val), NextState("RIGHT"), rx_wren.eq( 1 ) # Pulse rx_wren to write the current data word ).Else( NextValue(rx_delay_cnt, rx_delay_cnt - 1), NextState("LEFT_WAIT"))).Else( NextState("LEFT_WAIT"))).Elif( rx_cnt > 0, NextState("LEFT"))))) rxi2s.act( "RIGHT", If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else( NextValue(rx_wr_d, Cat(rx_pin, rx_wr_d[:-1])), NextValue(rx_cnt, rx_cnt - 1), NextState("RIGHT_WAIT"))) rxi2s.act( "RIGHT_WAIT", If(~self.rx_ctl.fields.enable, NextState("IDLE")).Else( If( rising_edge, If( (rx_cnt == 0) & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin), If( rx_delay_cnt == 0, NextValue(rx_cnt, sample_width), NextValue(rx_delay_cnt, rx_delay_val), NextState("LEFT"), rx_wren.eq( 1 ) # Pulse rx_wren to write the current data word ).Else(NextValue(rx_delay_cnt, rx_delay_cnt - 1), NextState("RIGHT_WAIT"))).Elif( rx_cnt > 0, NextState("RIGHT"))))) # Build the TX subsystem if hasattr(pads, 'tx'): tx_rd_d = Signal(fifo_data_width) tx_almostfull = Signal() tx_almostempty = Signal() tx_full = Signal() tx_empty = Signal() tx_rdcount = Signal(9) tx_rderr = Signal() tx_wrerr = Signal() tx_wrcount = Signal(9) tx_rden = Signal() tx_wr_d = Signal(fifo_data_width) tx_wren = Signal() self.tx_ctl = CSRStorage( description="Tx data path control", fields=[ CSRField("enable", size=1, description="Enable the transmission data"), CSRField( "reset", size=1, description= "Writing `1` resets the FIFO. Reset happens regardless of enable state.", pulse=1) ]) self.tx_stat = CSRStatus( description="Tx data path status", fields=[ CSRField("overflow", size=1, description="Tx overflow"), CSRField("underflow", size=1, description="Tx underflow"), CSRField( "free", size=1, description="At least {} words of space free".format( fifo_depth)), CSRField("almostfull", size=1, description="Less than 8 words space available" ), # the next few flags should be rarely used CSRField("full", size=1, description="FIFO is full or overfull"), CSRField("empty", size=1, description="FIFO is empty"), CSRField("wrcount", size=9, description="Tx write count"), CSRField("rdcount", size=9, description="Tx read count"), CSRField( "concatenate_channels", size=1, reset=concatenate_channels, description="Receive and send both channels atomically" ) ]) self.tx_conf = CSRStatus( description="TX configuration", fields=[ CSRField( "format", size=2, reset=frame_format.value, description= "I2S sample format. {} is left-justified, {} is I2S standard" .format(I2S_FORMAT.I2S_LEFT_JUSTIFIED, I2S_FORMAT.I2S_STANDARD)), CSRField("sample_width", size=6, reset=sample_width, description="Single sample width"), CSRField("lrck_freq", size=24, reset=lrck_freq, description="Audio sampling rate frequency"), ]) tx_rst_cnt = Signal(3) tx_reset = Signal() self.sync += [ If( self.tx_ctl.fields.reset, tx_rst_cnt.eq(5), # 5 cycles reset required by design tx_reset.eq(1)).Else( If(tx_rst_cnt == 0, tx_reset.eq(0)).Else(tx_rst_cnt.eq(tx_rst_cnt - 1), tx_reset.eq(1))) ] # At a width of 32 bits, an 18kiB fifo is 512 entries deep self.specials += Instance( "FIFO_SYNC_MACRO", p_DEVICE="7SERIES", p_FIFO_SIZE="18Kb", p_DATA_WIDTH=fifo_data_width, p_ALMOST_EMPTY_OFFSET=(512 - fifo_depth), p_ALMOST_FULL_OFFSET=8, p_DO_REG=0, i_CLK=ClockSignal(), i_RST=tx_reset, o_ALMOSTFULL=tx_almostfull, o_ALMOSTEMPTY=tx_almostempty, o_FULL=tx_full, o_EMPTY=tx_empty, i_WREN=tx_wren & ~tx_reset, i_DI=tx_wr_d, i_RDEN=tx_rden & ~tx_reset, o_DO=tx_rd_d, o_RDCOUNT=tx_rdcount, o_RDERR=tx_rderr, o_WRCOUNT=tx_wrcount, o_WRERR=tx_wrerr, ) self.comb += [ # Wire up the status signals and interrupts self.tx_stat.fields.underflow.eq(tx_rderr), self.tx_stat.fields.free.eq(tx_almostempty), self.tx_stat.fields.almostfull.eq(tx_almostfull), self.tx_stat.fields.full.eq(tx_full), self.tx_stat.fields.empty.eq(tx_empty), self.tx_stat.fields.rdcount.eq(tx_rdcount), self.tx_stat.fields.wrcount.eq(tx_wrcount), self.ev.tx_ready.trigger.eq(tx_almostempty), self.ev.tx_error.trigger.eq(tx_wrerr | tx_rderr), ] self.sync += [ # This is the bus responder -- need to check how this interacts with uncached memory # region If( bus.cyc & bus.stb & bus.we & ~bus.ack, If( ~tx_full, tx_wr_d.eq(bus.dat_w), tx_wren.eq(1), wr_ack.eq(1), ).Else( tx_wren.eq(0), wr_ack.eq(0), )).Else( tx_wren.eq(0), wr_ack.eq(0), ) ] tx_buf_width = fifo_data_width + 1 if frame_format == I2S_FORMAT.I2S_STANDARD else fifo_data_width sample_width = sample_width + 1 if frame_format == I2S_FORMAT.I2S_STANDARD else sample_width offset = [0] if frame_format == I2S_FORMAT.I2S_STANDARD else [] tx_cnt_width = math.ceil(math.log(fifo_data_width, 2)) tx_cnt = Signal(tx_cnt_width) tx_buf = Signal(tx_buf_width) sample_msb = fifo_data_width - 1 self.submodules.txi2s = txi2s = FSM(reset_state="IDLE") txi2s.act( "IDLE", If( self.tx_ctl.fields.enable, If( falling_edge & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin), NextState("WAIT_SYNC"), ))), txi2s.act( "WAIT_SYNC", If( falling_edge & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin), NextState("LEFT"), NextValue(tx_cnt, sample_width), NextValue(tx_buf, Cat(tx_rd_d, offset)), tx_rden.eq(1), )) txi2s.act( "LEFT", If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else( NextValue(tx_pin, tx_buf[sample_msb]), NextValue(tx_buf, Cat(0, tx_buf[:-1])), NextValue(tx_cnt, tx_cnt - 1), NextState("LEFT_WAIT"))) if concatenate_channels: txi2s.act( "LEFT_WAIT", If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else( If( falling_edge, If((tx_cnt == 0), If( (sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else ~sync_pin), NextValue(tx_cnt, sample_width), NextState("RIGHT"), ).Else(NextState("LEFT_WAIT"), )).Elif( tx_cnt > 0, NextState("LEFT"), )))) else: txi2s.act( "LEFT_WAIT", If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else( If( falling_edge, If((tx_cnt == 0), If( (sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else ~sync_pin), NextValue(tx_cnt, sample_width), NextState("RIGHT"), NextValue(tx_buf, Cat(tx_rd_d, offset)), tx_rden.eq(1), ).Else(NextState("LEFT_WAIT"), )).Elif( tx_cnt > 0, NextState("LEFT"), )))) txi2s.act( "RIGHT", If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else( NextValue(tx_pin, tx_buf[sample_msb]), NextValue(tx_buf, Cat(0, tx_buf[:-1])), NextValue(tx_cnt, tx_cnt - 1), NextState("RIGHT_WAIT"))) txi2s.act( "RIGHT_WAIT", If(~self.tx_ctl.fields.enable, NextState("IDLE")).Else( If( falling_edge, If((tx_cnt == 0) & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin), NextValue(tx_cnt, sample_width), NextState("LEFT"), NextValue(tx_buf, Cat(tx_rd_d, offset)), tx_rden.eq(1)).Elif(tx_cnt > 0, NextState("RIGHT")))))
def __init__(self, pads, gpio_cs=False): self.intro = ModuleDoc( """Simple soft SPI controller module optimized for Betrusted applications Requires a clock domain 'spi', which runs at the speed of the SPI bus. Simulation benchmarks 16.5us to transfer 16x16 bit words including setup overhead (sysclk=100MHz, spiclk=25MHz) which is about 15Mbps system-level performance, assuming the receiver can keep up. Note that for the ICE40 controller, timing simulations indicate the clock rate could go higher than 24MHz, although there is some question if setup/hold times to the external can be met after all delays are counted. The gpio_cs parameter when true turns CS into a GPIO to be managed by software; when false, it is automatically asserted/de-asserted by the SpiController machine. gpio_cs is {} in this instance. """.format(gpio_cs)) self.cipo = pads.cipo self.copi = pads.copi self.csn = pads.csn self.tx = CSRStorage(16, name="tx", description="""Tx data, for COPI""") self.rx = CSRStatus(16, name="rx", description="""Rx data, from CIPO""") if gpio_cs: self.cs = CSRStorage(fields=[ CSRField( "cs", description= "Writing `1` to this asserts cs_n, that is, brings it low; writing `0`, brings it high" ) ]) self.control = CSRStorage(fields=[ CSRField( "go", description= "Initiate a SPI cycle by writing a `1`. Does not automatically clear." ), ]) self.status = CSRStatus(fields=[ CSRField("tip", description="Set when transaction is in progress"), CSRField("txfull", description="Set when Tx register is full"), ]) self.wifi = CSRStorage(fields=[ CSRField( "reset", description= "Write `1` to this register to reset the wifi chip; write `0` to enable normal operation", reset=1), CSRField( "pa_ena", description= "Mapped to PA_ENABLE for wifi (only useful if configured in wifi firmware)" ), CSRField("wakeup", description="Wakeup the wifi chip"), ]) self.comb += [ pads.pa_enable.eq(self.wifi.fields.pa_ena), pads.wakeup.eq(self.wifi.fields.wakeup), pads.res_n.eq(~self.wifi.fields.reset), ] self.submodules.ev = EventManager() # self.ev.spi_int = EventSourceProcess(description="Triggered on conclusion of each transaction") # falling edge triggered self.ev.wirq = EventSourcePulse( description="Interrupt request from wifi chip" ) # rising edge triggered self.ev.finalize() # self.comb += self.ev.spi_int.trigger.eq(self.status.fields.tip) wirq_in = Signal() wirq_r = Signal() self.specials += MultiReg(pads.wirq, wirq_in) self.sync += wirq_r.eq(wirq_in) self.comb += self.ev.wirq.trigger.eq(wirq_in & ~wirq_r) tx_swab = Signal(16) self.comb += tx_swab.eq(Cat(self.tx.storage[8:], self.tx.storage[:8])) # Replica CSR into "spi" clock domain self.tx_r = Signal(16) self.rx_r = Signal(16) self.tip_r = Signal() self.txfull_r = Signal() self.go_r = Signal() self.tx_written = Signal() self.specials += MultiReg(self.tip_r, self.status.fields.tip) self.specials += MultiReg(self.txfull_r, self.status.fields.txfull) self.specials += MultiReg(self.control.fields.go, self.go_r, "spi") self.specials += MultiReg(self.tx.re, self.tx_written, "spi") # extract rising edge of go -- necessary in case of huge disparity in sysclk-to-spi clock domain self.go_d = Signal() self.go_edge = Signal() self.sync.spi += self.go_d.eq(self.go_r) self.comb += self.go_edge.eq(self.go_r & ~self.go_d) self.csn_r = Signal(reset=1) if gpio_cs: self.comb += self.csn.eq(~self.cs.fields.cs) else: self.comb += self.csn.eq(self.csn_r) self.comb += self.rx.status.eq( Cat(self.rx_r[8:], self.rx_r[:8])) ## invalid while transaction is in progress fsm = FSM(reset_state="IDLE") fsm = ClockDomainsRenamer("spi")(fsm) self.submodules += fsm spicount = Signal(4) spiclk_run = Signal() fsm.act( "IDLE", If( self.go_edge, NextState("RUN"), NextValue(self.tx_r, Cat(0, tx_swab[:15])), # stability guaranteed so no synchronizer necessary NextValue(spicount, 15), NextValue(self.txfull_r, 0), NextValue(self.tip_r, 1), NextValue(self.csn_r, 0), NextValue(self.copi, tx_swab[15]), NextValue(self.rx_r, Cat(self.cipo, self.rx_r[:15])), NextValue(spiclk_run, 1), ).Else( NextValue(spiclk_run, 0), NextValue(self.tip_r, 0), NextValue(self.csn_r, 1), If( self.tx_written, NextValue(self.txfull_r, 1), ), ), ) fsm.act( "RUN", If( spicount > 0, NextValue(spiclk_run, 1), NextValue(self.copi, self.tx_r[15]), NextValue(self.tx_r, Cat(0, self.tx_r[:15])), NextValue(spicount, spicount - 1), NextValue(self.rx_r, Cat(self.cipo, self.rx_r[:15])), ).Else( NextValue(spiclk_run, 0), NextValue(self.csn_r, 1), NextValue(self.tip_r, 0), NextState("IDLE"), ), ) # gate the spi clock so it only runs during a SPI transaction -- requirement of the wf200 block self.specials += [ Instance( "SB_IO", p_PIN_TYPE=0b100000, # define a DDR output type p_IO_STANDARD="SB_LVCMOS", p_PULLUP=0, p_NEG_TRIGGER=0, io_PACKAGE_PIN=pads.sclk, i_LATCH_INPUT_VALUE=0, i_CLOCK_ENABLE=1, i_INPUT_CLK=0, i_OUTPUT_CLK=ClockSignal("spi"), i_OUTPUT_ENABLE=1, i_D_OUT_0=0, # rising clock edge i_D_OUT_1=spiclk_run, # falling clock edge ) ]
def __init__(self, mem_payload, mem_scratchpad, dfi, dfi_sel, *, nranks, bankbits, rowbits, colbits, rdphase): self.description = ModuleDoc(""" Executes the DRAM payload from memory {} """.format(Decoder.__doc__)) self.start = Signal() self.ready = Signal() self.program_counter = Signal(max=mem_payload.depth - 1) self.loop_counter = Signal(Decoder.LOOP_COUNT) self.idle_counter = Signal(Decoder.TIMESLICE_NOOP) # Scratchpad self.submodules.scratchpad = Scratchpad(mem_scratchpad, dfi) # Fetcher # simple async reads, later we would probably want 1 cycle prefetch? instruction = Signal(Decoder.INSTRUCTION) payload_port = mem_payload.get_port(write_capable=False, async_read=True) self.specials += payload_port self.comb += [ payload_port.adr.eq(self.program_counter), instruction.eq(payload_port.dat_r), ] # Decoder rankbits = log2_int(nranks) self.submodules.decoder = decoder = Decoder(instruction, rankbits=rankbits, bankbits=bankbits, rowbits=rowbits, colbits=colbits) self.submodules.rank_decoder = OneHotDecoder(nranks) if rankbits: self.comb += self.rank_decoder.i.eq(self.decoder.dfi_rank) # Executor self.submodules.dfi_executor = DFIExecutor(dfi, self.decoder, self.rank_decoder) self.submodules.fsm = FSM() self.fsm.act( "READY", self.ready.eq(1), self.scratchpad.reset.eq(self.start), If( self.start, NextState("RUN"), NextValue(self.program_counter, 0), )) self.fsm.act( "RUN", dfi_sel.eq(1), # Always execute the whole program If(self.program_counter == mem_payload.depth - 1, NextState("READY")), # Execute instruction If( decoder.op_code == OpCode.LOOP, # If a loop instruction with count=0 is found it will be a NOOP If( self.loop_counter != decoder.loop_count, # Continue the loop NextValue(self.program_counter, self.program_counter - decoder.loop_jump), NextValue(self.loop_counter, self.loop_counter + 1), ).Else( # Finish the loop # Set loop_counter to 0 so that next loop instruction will start properly NextValue(self.program_counter, self.program_counter + 1), NextValue(self.loop_counter, 0), ), ).Else( # DFI instruction If(decoder.timeslice == 0, NextValue(self.program_counter, self.program_counter + 1)).Else( # Wait in idle loop after sending the command NextValue(self.idle_counter, decoder.timeslice - 1), NextState("IDLE"), ), # Send DFI command self.dfi_executor.exec.eq(1), If( decoder.cas & ~decoder.ras & ~decoder.we, # READ command self.dfi_executor.phase.eq(rdphase), ).Else(self.dfi_executor.phase.eq(0), )), ) self.fsm.act( "IDLE", dfi_sel.eq(1), If( self.idle_counter == 0, NextState("RUN"), NextValue(self.program_counter, self.program_counter + 1), ).Else(NextValue(self.idle_counter, self.idle_counter - 1), ))
def __init__(self, pads, debounce_ms=5): self.background = ModuleDoc("""Matrix Keyboard Driver A hardware key scanner that can run even when the CPU is powered down or stopped. The hardware is assumed to be a matrix of switches, divided into rows and columns. The number of rows and columns is inferred by the number of pins dedicated in the `pads` record. The rows are inputs, and require a `PULLDOWN` to be inferred on the pins, to prevent them from floating when the keys are not pressed. Note that `PULLDOWN` is not offered on all FPGA architectures, but at the very least is present on 7-Series FPGAs. The columns are driven by tristate drivers. The state of the columns is either driven high (`1`), or hi-Z (`Z`). The KeyScan module also expects a `kbd` clock domain. The preferred implementation makes this a 32.768KHz always-on clock input with no PLL. This allows the keyboard module to continue scanning even if the CPU is powered down. Columns are scanned sequentially using a `kbd`-domain state machine by driving each column in order. When a column is driven, its electrical state goes from `hi-z` to `1`. The rows are then sampled with each column scan state. Since each row has a pulldown on it, if no keys are hit, the result is `0`. When a key is pressed, it will short a row to a column, and the `1` driven on a column will flip the row state to a `1`, thus registering a key press. Columns are driven for a minimum period of time known as a `settling` period. The settling time must be at least 2 because a Multireg (2-stage synchronizer) is used to sample the data on the receiving side. In this module, settling time is fixed to `4` cycles. Thus a 1 in the `rowdat` registers indicate the column intersection that was active for a given row in the matrix. There is also a row shadow register that is maintained by the hardware. The row shadow is used to maintain the previous state of the key matrix, so that a "key press change" interrupt can be generated. There is also a `keypressed` interrupt which fires every time there is a change in the row registers. `debounce` is the period in ms for the keyboard matrix to stabilize before triggering an interrupt. """) rows_unsync = pads.row cols = Signal(pads.col.nbits) self.uart_inject = Signal(8) # character to inject from the UART self.inject_strobe = Signal() # on rising edge, latch uart_inject and raise an interrupt self.uart_char = CSRStatus(9, fields = [ CSRField("char", size=8, description="character value being injected"), CSRField("stb", size=1, description="FIFO has readable characters"), ]) self.submodules.injectfifo = injectfifo = fifo.SyncFIFOBuffered(width=8, depth=16) self.comb += [ injectfifo.din.eq(self.uart_inject), injectfifo.we.eq(self.inject_strobe), #self.uart_char.fields.char.eq(self.uart_inject), self.uart_char.fields.char.eq(injectfifo.dout), self.uart_char.fields.stb.eq(injectfifo.readable), injectfifo.re.eq(self.uart_char.we), ] for c in range(0, cols.nbits): cols_ts = TSTriple(1) self.specials += cols_ts.get_tristate(pads.col[c]) self.comb += cols_ts.oe.eq(cols[c]) self.comb += cols_ts.o.eq(1) # row and col are n-bit signals that correspond to the row and columns of the keyboard matrix # each row will generate a column register with the column result in it rows = Signal(rows_unsync.nbits) self.specials += MultiReg(rows_unsync, rows, "kbd") # setattr(self, name, object) is the same as self.name = object, except in this case "name" can be dynamically generated # this is necessary here because we CSRStatus is not iterable, so we have to manage the attributes manually for r in range(0, rows.nbits): setattr(self, "row" + str(r) + "dat", CSRStatus(cols.nbits, name="row" + str(r) + "dat", description="""Column data for the given row""")) settling = 4 # 4 cycles to settle: 2 cycles for MultiReg stabilization + slop. Must be > 2, and a power of 2 colcount = Signal(max=(settling*cols.nbits+2)) update_shadow = Signal() reset_scan = Signal() scan_done = Signal() col_r = Signal(cols.nbits) scan_done_sys = Signal() self.specials += MultiReg(scan_done, scan_done_sys) for r in range(0, rows.nbits): row_scan = Signal(cols.nbits) row_scan_sys = Signal(cols.nbits) # below is in sysclock domain; row_scan is guaranteed stable by state machine sequencing when scan_done gating is enabled self.sync += [ row_scan_sys.eq(row_scan), # loosen up the clock domain crossing timing If(scan_done_sys, getattr(self, "row" + str(r) + "dat").status.eq(row_scan_sys) ).Else( getattr(self, "row" + str(r) + "dat").status.eq(getattr(self, "row" + str(r) + "dat").status) ) ] self.sync.kbd += [ If(reset_scan, row_scan.eq(0) ).Else( If(rows[r] & (colcount[0:2] == 3), # sample row only on the 4th cycle of colcount row_scan.eq(row_scan | col_r) ).Else( row_scan.eq(row_scan) ) ) ] rowshadow = Signal(cols.nbits) self.sync.kbd += If(update_shadow, rowshadow.eq(row_scan)).Else(rowshadow.eq(rowshadow)) setattr(self, "row_scan" + str(r), row_scan) setattr(self, "rowshadow" + str(r), rowshadow) # create a simple, one-scan delayed version of row_scan for debouncing purposes row_debounce = Signal(cols.nbits) self.sync.kbd += [ If(scan_done, row_debounce.eq(row_scan), ).Else( row_debounce.eq(row_debounce), ) ] setattr(self, "row_debounce" + str(r), row_debounce) pending_kbd = Signal() pending_kbd_f = Signal() key_ack = Signal() self.sync.kbd += [ pending_kbd_f.eq(pending_kbd), colcount.eq(colcount + 1), scan_done.eq(0), update_shadow.eq(0), reset_scan.eq(0), If(colcount == (settling*cols.nbits+2), colcount.eq(0)), If(colcount == (settling*cols.nbits), scan_done.eq(1)), # Only update the shadow if the pending bit has been cleared (e.g., CPU has acknowledged # it has fetched the current key state) If(~pending_kbd & pending_kbd_f, If(colcount == (settling * cols.nbits + 1), update_shadow.eq(1), key_ack.eq(0), ).Else( key_ack.eq(1), ) ).Else( If(colcount == (settling*cols.nbits+1), update_shadow.eq(key_ack), key_ack.eq(0), ).Else( key_ack.eq(key_ack) ) ), If(colcount == (settling*cols.nbits+2), reset_scan.eq(1)), ] # Drive the columns based on the colcount counter self.submodules.coldecoder = Decoder(cols.nbits) self.comb += [ self.coldecoder.i.eq(colcount[log2_int(settling):]), self.coldecoder.n.eq(~(colcount < settling*cols.nbits)), cols.eq(self.coldecoder.o) ] self.sync.kbd += col_r.eq(self.coldecoder.o) self.submodules.ev = EventManager() self.ev.keypressed = EventSourcePulse(description="Triggered every time there is a difference in the row state") # Rising edge triggered self.ev.inject = EventSourcePulse(description="Key injection request received") self.ev.finalize() self.sync += [ self.ev.inject.trigger.eq(self.inject_strobe) ] # zero state auto-clear: the "delta" methodology gets stuck if the initial sampling state of the keyboard matrix isn't 0. this fixes that. rows_nonzero = Signal(rows.nbits) col_zeros = Signal(cols.nbits) # form multi-bit zeros of the right width, otherwise we get 1'd0 as the rhs of equality statements row_zeros = Signal(rows.nbits) for r in range(0, rows.nbits): self.sync.kbd += rows_nonzero[r].eq(getattr(self, "row_scan" + str(r)) != col_zeros) all_zeros = Signal() clear_deltas = Signal() self.comb += all_zeros.eq(rows_nonzero == row_zeros) clear_kbd = Signal() self.submodules.clear_sync = BlindTransfer("sys", "kbd") self.comb += [ self.clear_sync.i.eq(self.ev.keypressed.clear), clear_kbd.eq(self.clear_sync.o) ] # debounce timers and clocks debounce_clocks = int((debounce_ms * 0.001) * 32768.0) debounce_timer = Signal(max=(debounce_clocks + 1)) debounced = Signal() # Extract any changes just before the shadow takes its new values rowdiff = Signal(rows.nbits) for r in range(0, rows.nbits): self.sync.kbd += [ If(clear_deltas, rowdiff[r].eq(0), ).Elif(debounced, # was scan_done rowdiff[r].eq( ~((getattr(self, "row_scan" + str(r)) ^ getattr(self, "rowshadow" + str(r))) == 0) ) ).Else( rowdiff[r].eq(rowdiff[r]) ) ] # debouncing: # 1. compute the delta of the current scan vs. previous scan # 2. if the delta is non-zero, start a debounce timer. # 3. Count down as long as the delta remains zero; if the delta is non-zero again, reset the timer. # 4. When the timer hits zero, latch the final value for sampling to the CPU rowchanging = Signal(rows.nbits) for r in range(0, rows.nbits): self.sync.kbd += [ If(clear_deltas, rowchanging[r].eq(0), ).Elif(scan_done, rowchanging[r].eq( ~((getattr(self, "row_scan" + str(r)) ^ getattr(self, "row_debounce" + str(r))) == 0) ) ).Else( rowchanging[r].eq(rowchanging[r]) ) ] db_fsm = ClockDomainsRenamer("kbd")(FSM(reset_state="IDLE")) self.submodules += db_fsm db_fsm.act("IDLE", If(rowchanging != 0, NextValue(debounce_timer, debounce_clocks), NextState("DEBOUNCING") ) ) db_fsm.act("DEBOUNCING", If(rowchanging == 0, NextValue(debounce_timer, debounce_timer - 1), If(debounce_timer == 0, NextState("DEBOUNCED") ) ).Else( NextValue(debounce_timer, debounce_clocks), NextState("DEBOUNCING") ) ) db_fsm.act("DEBOUNCED", If(scan_done, debounced.eq(1), NextState("IDLE") ) ) self.comb += clear_deltas.eq(all_zeros & clear_kbd & db_fsm.ongoing("IDLE")) # Fire an interrupt during the reset_scan phase. changed = Signal() changed_sys = Signal() kp_r = Signal() changed_kbd = Signal() debounced_d = Signal() self.sync.kbd += debounced_d.eq(debounced) self.kbd_wakeup = Signal() self.comb += changed.eq(debounced_d & (rowdiff != 0)) self.sync.kbd += [ If(changed, changed_kbd.eq(1) ).Elif(key_ack, changed_kbd.eq(0) ).Else( changed_kbd.eq(changed_kbd) ) ] self.comb += self.kbd_wakeup.eq(changed_kbd | changed) self.specials += MultiReg(changed_kbd, changed_sys) self.sync += kp_r.eq(changed_sys) self.comb += self.ev.keypressed.trigger.eq(changed_sys & ~kp_r) self.submodules.pending_sync = BlindTransfer("sys", "kbd") # pulse-convert pending into a single pulse on the falling edge -- otherwise we get a pulse train kp_pending_sys = Signal() kp_pending_sys_r = Signal() self.sync += [ kp_pending_sys.eq(self.ev.keypressed.pending), kp_pending_sys_r.eq(kp_pending_sys), ] self.comb += [ self.pending_sync.i.eq(~kp_pending_sys & kp_pending_sys_r), pending_kbd.eq(self.pending_sync.o), ]
def __init__(self, clkspertick, clkfreq, bits=64): self.clkspertick = int(clkfreq / clkspertick) self.intro = ModuleDoc("""TickTimer: A practical systick timer. TIMER0 in the system gives a high-resolution, sysclk-speed timer which overflows very quickly and requires OS overhead to convert it into a practically usable time source which counts off in systicks, instead of sysclks. The hardware parameter to the block is the divisor of sysclk, and sysclk. So if the divisor is 1000, then the increment for a tick is 1ms. If the divisor is 2000, the increment for a tick is 0.5ms. """) resolution_in_ms = 1000 * (self.clkspertick / clkfreq) self.note = ModuleDoc( title="Configuration", body= "This timer was configured with {} bits, which rolls over in {:.2f} years, with each bit giving {}ms resolution" .format(bits, (2**bits / (60 * 60 * 24 * 365)) * (self.clkspertick / clkfreq), resolution_in_ms)) prescaler = Signal(max=self.clkspertick, reset=self.clkspertick) timer = Signal(bits) self.control = CSRStorage( 2, fields=[ CSRField("reset", description= "Write a `1` to this bit to reset the count to 0", pulse=True), CSRField( "pause", description= "Write a `1` to this field to pause counting, 0 for free-run" ) ]) self.time = CSRStatus(bits, name="time", description="""Elapsed time in systicks""") self.sync += [ If( self.control.fields.reset, timer.eq(0), prescaler.eq(self.clkspertick), ).Else( If(prescaler == 0, prescaler.eq(self.clkspertick), If( self.control.fields.pause == 0, timer.eq(timer + 1), )).Else(prescaler.eq(prescaler - 1), )) ] self.comb += self.time.status.eq(timer) self.msleep = ModuleDoc("""msleep extension The msleep extension is a Xous-specific add-on to aid the implementation of the msleep server. msleep fires an interrupt when the requested time is less than or equal to the current elapsed time in systicks. The interrupt remains active until a new target is set, or masked. """) self.msleep_target = CSRStorage( size=bits, description="Target time in {}ms ticks".format(resolution_in_ms)) self.submodules.ev = EventManager() alarm = Signal() self.ev.alarm = EventSourceLevel() self.comb += self.ev.alarm.trigger.eq(alarm) self.comb += alarm.eq(self.msleep_target.storage <= timer)
def __init__(self, pads): self.background = ModuleDoc( """MemLCD: Driver for the SHARP Memory LCD model LS032B7DD02 The LS032B7DD02 is a 336x536 pixel black and white memory LCD, with a 200ppi dot pitch. Memory LCDs can be thought of as 'fast E-ink displays that consume a tiny bit of standby power', as seen by these properties: * Extremely low standby power (30uW typ hold mode) * No special bias circuitry required to maintain image in hold mode * 120 degree viewing angle, 1:35 contrast ratio * All control logic fabricated on-glass using TFT devices that are auditable with a common 40x power desktop microscope and a bright backlight source This last property in particular makes the memory LCD extremely well suited for situations where absolute visibility into the construction of a secure enclave is desired. The memory organization of the LS032B7DD02 is simple: 536 lines of pixel data 336 wide. Each pixel is 1 bit (the display is black and white), and is fed into the module from left to right as pixels 1 through 336, inclusive. Lines are enumerated from top to bottom, from 1 to 536, inclusive. The LCD can only receive serial data. The protocol is a synchronous serial interface with an active high chip select. All data words are transmitted LSB first. A line transfer is initiated by sending a 6-bit mode selection, a 10-bit row address, and the subsequent 336 pixels, followed by 16 dummy bits which transfer the data from the LCD holding register to the display itself. .. wavedrom:: :caption: Single line data transfer to memory LCD { "signal": [ { "name": "SCLK", "wave": "0.P.......|......|...|..l." }, { "name": "SCS", "wave": "01........|......|...|.0.." }, { "name": "SI", "wave": "0===x..===|======|==x|....", "data": ["M0", "M1", "M2", "R0", "R1", " ", "R8", "R9", "D0", "D1", "D2", " ", "D334", "D335"] }, { "node": ".....................a.b..."}, ], "edge": ['a<->b 16 cycles'] } Alternatively, one can send successive lines without dropping SCS by substituting the 16 dummy bits at the end with a 6-bit don't care preamble (where the mode bits would have been), 10 bits of row address, and then the pixel data. .. wavedrom:: :caption: Multiple line data transfer to memory LCD { "signal": [ { "name": "SCLK", "wave": "0.P.......|......|...|....|....." }, { "name": "SCS", "wave": "01........|......|...|....|....." }, { "name": "SI", "wave": "0===x..===|======|==x|.===|=====", "data": ["M0", "M1", "M2", "R0", "R1", " ", "R8", "R9", "D0", "D1", "D2", " ", "D334", "D335", "R0", "R1", " ", "R8", "R9", "D0", "D1"] }, { "node": ".....................a.b..."}, ], "edge": ['a<->b 6 cycles'] } The very last line in the multiple line data transfer must terminate with 16 dummy cycles. Mode bits M0-M2 have the following meaning: M0: Set to 1 when transferring data lines. Set to 0 for hold mode, see below. M1: VCOM inversion flag. Ignore when hardware strap pin EXTMODE is high. Betrusted sets EXTMODE high, so the VCOM inversion is handled by low-power aux hardware. When EXTMODE is low, software must explicitly manage the VCOM inversion flag such the flag polarity changes once every second "as much as possible". M2: Normally set to 0. Set to 1 for all clear (see below) Data bit polarity: 1 = White 0 = Black For 'Hold mode' and 'All clear', a total of 16 cycles are sent, the first three being the mode bit and the last 13 being dummy cycles. .. wavedrom:: :caption: Hold and all clear timings { "signal": [ { "name": "SCLK", "wave": "0.P...|.l" }, { "name": "SCS", "wave": "01....|0." }, { "name": "SI", "wave": "0===x...", "data": ["M0", "M1", "M2", "R0", "R1", " ", "R8", "R9", "D0", "D1", "D2", " ", "D334", "D335", "R0", "R1", " ", "R8", "R9", "D0", "D1"] }, { "node": ".....a.b..."}, ], "edge": ['a<->b 13 cycles'] } All signals are 3.0V compatible, 5V tolerant (VIH is 2.7V-VDD). The display itself requires a single 5V power supply (4.8-5.5V typ 5.8V abs max). In hold mode, typical power is 30uW, max 330uW; with data updating at 1Hz, power is 250uW, max 750uW (SCLK=1MHz, EXTCOMMIN=1Hz). * The maximum clock frequency for SCLK is 2MHz (typ 1MHz). * EXTCOMMIN frequency is 1-10Hz, 1Hz typ * EXTCOMMIN minimum high duration is 1us * All rise/fall times must be less than 50ns * SCS setup time is 3us, hold time is 1us. Minimum low duration is 1us, minimum high is 188us for a data update, 12 us for a hold mode operation. * SI setup time is 120ns, 190ns hold time. * Operating temperature is -20 to 70C, storage temperature -30 to 80C. """) self.interface = ModuleDoc("""Wishbone interface for MemLCD MemLCD maintains a local framebuffer for the LCD. The CPU can read and write to the frame buffer to update pixel data, and then request a screen update to commit the frame buffer to the LCD. Only full lines can be updated on a memory LCD; partial updates are not possible. In order to optimize the update process, MemLCD maintains a "dirty bit" associated with each line. Only lines with modified pixels are written to the screen after an update request. A line is 336 bits wide. When padded to 32-bit words, this yields a line width of 44 bytes (0x2C, or 352 bits). In order to simplify math, the frame buffer rounds the line width up to the nearest power of two, or 64 bytes. The unused bits can be used as a "hint" to the MemLCD block as to which lines require updating. If the unused bits have any value other than 0, the MemLCD block will update those lines when an "UpdateDirty" command is triggered. It is up to the CPU to set and clear the dirty bits, they are not automatically cleared by the block upon update. Typically the clearing of the bits would be handled during the update-finished interrupt handling routine. If the dirty bits are not used, an "UpdateAll" command can be invoked, which will update every line of the LCD regardless of the contents of the dirty bits. The total depth of the memory is thus 44 bytes * 536 lines = 23,584 bytes or 5,896 words. Pixels are stored with the left-most pixel in the MSB of each 32-bit word, with the left-most pixels occupying the lowest address in the line. Lines are stored with the bottom line of the screen at the lowest address. These parameters are chosen so that a 1-bit BMP file can be copied into the frame buffer and it will render directly to the screen with no further transformations required. The CPU is responsible for not writing data to the LCD while it is updating. Concurrent writes to the LCD during updates can lead to unpredictable behavior. """) data_width = 32 width = 336 height = 536 bytes_per_line = 44 self.fb_depth = fb_depth = height * bytes_per_line // (data_width // 8) pixdata = Signal(32) pixadr_rd = Signal(max=fb_depth) # 1 is white, which is the "off" state fb_init = [0xffffffff] * int(fb_depth) for i in range(fb_depth // 11): fb_init[i * 11 + 10] = 0xffff mem = Memory( 32, fb_depth, init=fb_init ) # may need to round up to 8192 if a power of 2 is required by migen # read port for pixel data out self.specials.rdport = mem.get_port( write_capable=False, mode=READ_FIRST) # READ_FIRST allows BRAM to be used self.comb += self.rdport.adr.eq(pixadr_rd) self.comb += pixdata.eq(self.rdport.dat_r) # implementation note: vivado will complain about being unable to merge an output register, leading to # non-optimal timing, but a check of the timing path shows that at 100MHz there is about 4-5ns of setup margin, # so the merge is unnecessary in this case. Ergo, prefer comb over sync to reduce latency. # memory-mapped write port to wishbone bus self.bus = wishbone.Interface() self.submodules.wb_sram_if = wishbone.SRAM(mem, read_only=False) decoder_offset = log2_int(fb_depth, need_pow2=False) def slave_filter(a): return a[decoder_offset:32 - decoder_offset] == 0 # no aliasing in the block self.submodules.wb_con = wishbone.Decoder( self.bus, [(slave_filter, self.wb_sram_if.bus)], register=True) self.command = CSRStorage( 2, fields=[ CSRField( "UpdateDirty", description="Write a ``1`` to flush dirty lines to the LCD", pulse=True), CSRField( "UpdateAll", description="Update full screen regardless of tag state", pulse=True), ]) self.busy = CSRStatus( 1, name="Busy", description= """A ``1`` indicates that the block is currently updating the LCD""" ) self.prescaler = CSRStorage(8, reset=99, name="prescaler", description=""" Prescaler value. LCD clock is module (clock / (prescaler+1)). Reset value: 99, so for a default sysclk of 100MHz this yields an LCD SCLK of 1MHz""") self.submodules.ev = EventManager() self.ev.done = EventSourceProcess() self.ev.finalize() self.comb += self.ev.done.trigger.eq( self.busy.status) # Fire an interupt when busy drops self.sclk = sclk = getattr(pads, "sclk") self.scs = scs = getattr(pads, "scs") self.si = si = getattr(pads, "si") self.sendline = sendline = Signal() self.linedone = linedone = Signal() updateall = Signal() fetch_dirty = Signal() update_line = Signal( max=height ) # Keep track of both line and address to avoid instantiating a multiplier update_addr = Signal(max=height * bytes_per_line) fsm_up = FSM(reset_state="IDLE") self.submodules += fsm_up fsm_up.act( "IDLE", If( self.command.fields.UpdateDirty | self.command.fields.UpdateAll, NextValue(self.busy.status, 1), NextValue(fetch_dirty, 1), If(self.command.fields.UpdateAll, NextValue(updateall, 1)).Else(NextValue(updateall, 0)), NextState("START")).Else(NextValue(self.busy.status, 0))) fsm_up.act( "START", NextValue(update_line, height), NextValue( update_addr, (height - 1) * bytes_per_line ), # Represents the byte address of the beginning of the last line NextState("FETCHDIRTY")) fsm_up.act( "FETCHDIRTY", # Wait one cycle delay for the pixel data to be retrieved before evaluating it NextState("CHECKDIRTY")) fsm_up.act( "CHECKDIRTY", If(update_line == 0, NextState("IDLE")).Else( If( (pixdata[16:] != 0) | updateall, NextState("DIRTYLINE"), ).Else(NextValue(update_line, update_line - 1), NextValue(update_addr, update_addr - bytes_per_line), NextState("FETCHDIRTY")))) fsm_up.act("DIRTYLINE", NextValue(fetch_dirty, 0), sendline.eq(1), NextState("WAITDONE")) fsm_up.act( "WAITDONE", If(linedone, NextValue(fetch_dirty, 1), NextValue(update_line, update_line - 1), NextValue(update_addr, update_addr - bytes_per_line), NextState("FETCHDIRTY"))) modeshift = Signal(16) mode = Signal(6) pixshift = Signal(32) pixcount = Signal(max=width) bitreq = Signal() bitack = Signal() self.comb += mode.eq( 1 ) # Always in line write mode, not clearing, no vcom management necessary fsm_phy = FSM(reset_state="IDLE") self.submodules += fsm_phy # Update_addr units is in bytes. [2:] turns bytes to words # pixcount units are in pixels. [3:] turns pixels to bytes self.comb += [ If(fetch_dirty, pixadr_rd.eq( (update_addr + bytes_per_line - 4)[2:])).Else( pixadr_rd.eq((update_addr + pixcount[3:])[2:])) ] scs_cnt = Signal(max=200) fsm_phy.act( "IDLE", NextValue(si, 0), NextValue(linedone, 0), If( sendline, NextValue(scs, 1), NextValue(scs_cnt, 200), # 2 us setup NextValue(pixcount, 16), NextValue(modeshift, Cat(mode, update_line)), NextState("SCS_SETUP")).Else(NextValue(scs, 0))) fsm_phy.act( "SCS_SETUP", If(scs_cnt > 0, NextValue(scs_cnt, scs_cnt - 1)).Else(NextState("MODELINE"))) fsm_phy.act( "MODELINE", If(pixcount > 0, NextValue(modeshift, modeshift[1:]), NextValue(si, modeshift[0]), NextValue(pixcount, pixcount - 1), bitreq.eq(1), NextState("MODELINEWAIT")).Else(NextValue(pixcount, 1), NextValue(pixshift, pixdata), NextState("DATA"))) fsm_phy.act("MODELINEWAIT", If(bitack, NextState("MODELINE"))) fsm_phy.act( "DATA", If( pixcount < width + 17, If( pixcount[0:5] == 0, NextValue(pixshift, pixdata), ).Else(NextValue(pixshift, pixshift[1:]), ), NextValue(scs, 1), NextValue(si, pixshift[0]), NextValue(pixcount, pixcount + 1), bitreq.eq(1), NextState("DATAWAIT")).Else( NextValue(si, 0), NextValue(scs_cnt, 100), # 1 us hold NextState("SCS_HOLD"))) fsm_phy.act( "SCS_HOLD", If(scs_cnt > 0, NextValue(scs_cnt, scs_cnt - 1)).Else( NextValue(scs, 0), NextValue(scs_cnt, 100), # 1us minimum low time NextState("SCS_LOW"))) fsm_phy.act( "SCS_LOW", If(scs_cnt > 0, NextValue(scs_cnt, scs_cnt - 1)).Else(NextValue(linedone, 1), NextState("IDLE"))) fsm_phy.act("DATAWAIT", If(bitack, NextState("DATA"))) # This handles clock division fsm_bit = FSM(reset_state="IDLE") self.submodules += fsm_bit clkcnt = Signal(8) fsm_bit.act("IDLE", NextValue(sclk, 0), NextValue(clkcnt, self.prescaler.storage), If(bitreq, NextState("SCLK_LO"))) fsm_bit.act( "SCLK_LO", NextValue(clkcnt, clkcnt - 1), If(clkcnt < self.prescaler.storage[1:], NextValue(sclk, 1), NextState("SCLK_HI"))) fsm_bit.act( "SCLK_HI", NextValue(clkcnt, clkcnt - 1), If(clkcnt == 0, NextValue(sclk, 0), NextState("IDLE"), bitack.eq(1)))
def __init__(self, pads): self.background = ModuleDoc("""Matrix Keyboard Driver A hardware key scanner that can run even when the CPU is powered down or stopped. The hardware is assumed to be a matrix of switches, divided into rows and columns. The number of rows and columns is inferred by the number of pins dedicated in the `pads` record. The rows are inputs, and require a `PULLDOWN` to be inferred on the pins, to prevent them from floating when the keys are not pressed. Note that `PULLDOWN` is not offered on all FPGA architectures, but at the very least is present on 7-Series FPGAs. The columns are driven by tristate drivers. The state of the columns is either driven high (`1`), or hi-Z (`Z`). The KeyScan module also expects a `kbd` clock domain. The preferred implementation makes this a 32.768KHz always-on clock input with no PLL. This allows the keyboard module to continue scanning even if the CPU is powered down. Columns are scanned sequentially using a `kbd`-domain state machine by driving each column in order. When a column is driven, its electrical state goes from `hi-z` to `1`. The rows are then sampled with each column scan state. Since each row has a pulldown on it, if no keys are hit, the result is `0`. When a key is pressed, it will short a row to a column, and the `1` driven on a column will flip the row state to a `1`, thus registering a key press. Columns are driven for a minimum period of time known as a `settling` period. The settling time must be at least 2 because a Multireg (2-stage synchronizer) is used to sample the data on the receiving side. In this module, settling time is fixed to `4` cycles. Thus a 1 in the `rowdat` registers indicate the column intersection that was active for a given row in the matrix. There is also a row shadow register that is maintained by the hardware. The row shadow is used to maintain the previous state of the key matrix, so that a "key press change" interrupt can be generated. The first change in the row registers is recorded in the `rowchange` status register. It does not update until it has been read. The idea of this register is that it can capture one key hit while the CPU is in standby, and the CPU should wake up in time to read it, before a new key hit is registered. The CPU can consult the `rowchange` register to read just the `rowdat` CSR with the key change, instead of having to iterate through the entire array to find the row that changed. There is also a `keypressed` interrupt which fires every time there is a change in the row registers. """) rows_unsync = pads.row cols = Signal(pads.col.nbits) for c in range(0, cols.nbits): cols_ts = TSTriple(1) self.specials += cols_ts.get_tristate(pads.col[c]) self.comb += cols_ts.oe.eq(cols[c]) self.comb += cols_ts.o.eq(1) # row and col are n-bit signals that correspond to the row and columns of the keyboard matrix # each row will generate a column register with the column result in it rows = Signal(rows_unsync.nbits) self.specials += MultiReg(rows_unsync, rows, "kbd") # setattr(self, name, object) is the same as self.name = object, except in this case "name" can be dynamically generated # this is necessary here because we CSRStatus is not iterable, so we have to manage the attributes manually for r in range(0, rows.nbits): setattr( self, "row" + str(r) + "dat", CSRStatus(cols.nbits, name="row" + str(r) + "dat", description="""Column data for the given row""")) settling = 4 # 4 cycles to settle: 2 cycles for MultiReg stabilization + slop. Must be > 2, and a power of 2 colcount = Signal(max=(settling * cols.nbits + 2)) update_shadow = Signal() reset_scan = Signal() scan_done = Signal() col_r = Signal(cols.nbits) scan_done_sys = Signal() self.specials += MultiReg(scan_done, scan_done_sys) for r in range(0, rows.nbits): row_scan = Signal(cols.nbits) # below is in sysclock domain; row_scan is guaranteed stable by state machine sequencing when scan_done gating is enabled self.sync += [ If(scan_done_sys, getattr(self, "row" + str(r) + "dat").status.eq(row_scan)).Else( getattr(self, "row" + str(r) + "dat").status.eq( getattr(self, "row" + str(r) + "dat").status)) ] self.sync.kbd += [ If(reset_scan, row_scan.eq(0)).Else( If( rows[r] & (colcount[0:2] == 3), # sample row only on the 4th cycle of colcount row_scan.eq(row_scan | col_r)).Else( row_scan.eq(row_scan))) ] rowshadow = Signal(cols.nbits) self.sync.kbd += If(update_shadow, rowshadow.eq(row_scan)).Else( rowshadow.eq(rowshadow)) setattr(self, "row_scan" + str(r), row_scan) setattr(self, "rowshadow" + str(r), rowshadow) pending_key = Signal() self.sync.kbd += [ colcount.eq(colcount + 1), scan_done.eq(0), update_shadow.eq(0), reset_scan.eq(0), If(colcount == (settling * cols.nbits + 2), colcount.eq(0)), If(colcount == (settling * cols.nbits), scan_done.eq(1)), # Only update the shadow if the pending bit has been cleared (e.g., CPU has acknowledged # it has fetched the current key state) If(colcount == (settling * cols.nbits + 1), update_shadow.eq(~pending_key)), If(colcount == (settling * cols.nbits + 2), reset_scan.eq(1)), ] # Drive the columns based on the colcount counter self.submodules.coldecoder = Decoder(cols.nbits) self.comb += [ self.coldecoder.i.eq(colcount[log2_int(settling):]), self.coldecoder.n.eq(~(colcount < settling * cols.nbits)), cols.eq(self.coldecoder.o) ] self.sync.kbd += col_r.eq(self.coldecoder.o) self.submodules.ev = EventManager() self.ev.keypressed = EventSourcePulse( description= "Triggered every time there is a difference in the row state" ) # Rising edge triggered self.ev.finalize() # Extract any changes just before the shadow takes its new values rowdiff = Signal(rows.nbits) for r in range(0, rows.nbits): self.sync.kbd += [ If( scan_done, rowdiff[r].eq(~( (getattr(self, "row_scan" + str(r)) ^ getattr(self, "rowshadow" + str(r))) == 0))).Else( rowdiff[r].eq(rowdiff[r])) ] # Fire an interrupt during the reset_scan phase. Delay by 2 cycles so that rowchange can pick # up a new value before the "pending" bit is set. kp_d = Signal() kp_d2 = Signal() kp_r = Signal() kp_r2 = Signal() self.sync.kbd += kp_d.eq(rowdiff != 0) self.sync.kbd += kp_d2.eq(kp_d) self.sync += kp_r.eq(kp_d2) self.sync += kp_r2.eq(kp_r) self.comb += self.ev.keypressed.trigger.eq(kp_r & ~kp_r2) self.rowchange = CSRStatus( rows.nbits, name="rowchange", description= """The rows that changed at the point of interrupt generation. Does not update again until the interrupt is serviced.""") reset_scan_sys = Signal() self.specials += MultiReg(reset_scan, reset_scan_sys) self.sync += [ If(reset_scan_sys & ~self.ev.keypressed.pending, self.rowchange.status.eq(rowdiff)).Else( self.rowchange.status.eq(self.rowchange.status)) ] self.specials += MultiReg(self.ev.keypressed.pending, pending_key, "kbd")
def __init__(self, model, models, seed): """Create a `Version` block. Arguments: model (int): The value of the :obj:`CSRStatus(model)` field. models: a list() of (value, "shortname", "description") tuples. """ self.intro = ModuleDoc("""SoC Version Information This block contains various information about the state of the source code repository when this SoC was built. """) def makeint(i, base=10): try: return int(i, base=base) except: return 0 def get_gitver(): def decode_version(v): version = v.split(".") major = 0 minor = 0 rev = 0 if len(version) >= 3: rev = makeint(version[2]) if len(version) >= 2: minor = makeint(version[1]) if len(version) >= 1: major = makeint(version[0]) return (major, minor, rev) git_rev_cmd = subprocess.Popen( ["git", "describe", "--tags", "--dirty=+"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) (git_stdout, _) = git_rev_cmd.communicate() if git_rev_cmd.wait() != 0: print('unable to get git version') return raw_git_rev = git_stdout.decode().strip() dirty = 0 if raw_git_rev[-1] == "+": raw_git_rev = raw_git_rev[:-1] dirty = 1 parts = raw_git_rev.split("-") major = 0 minor = 0 rev = 0 gitrev = 0 gitextra = 0 if len(parts) >= 3: if parts[0].startswith("v"): version = parts[0] if version.startswith("v"): version = parts[0][1:] (major, minor, rev) = decode_version(version) gitextra = makeint(parts[1]) if parts[2].startswith("g"): gitrev = makeint(parts[2][1:], base=16) elif len(parts) >= 2: if parts[1].startswith("g"): gitrev = makeint(parts[1][1:], base=16) version = parts[0] if version.startswith("v"): version = parts[0][1:] (major, minor, rev) = decode_version(version) elif len(parts) >= 1: version = parts[0] if version.startswith("v"): version = parts[0][1:] (major, minor, rev) = decode_version(version) return (major, minor, rev, gitrev, gitextra, dirty) (major, minor, rev, gitrev, gitextra, dirty) = get_gitver() self.major = CSRStatus( 8, reset=major, description= "Major git tag version. For example, this firmware was built from git tag ``v{}.{}.{}``, so this value is ``{}``." .format(major, minor, rev, major)) self.minor = CSRStatus( 8, reset=minor, description= "Minor git tag version. For example, this firmware was built from git tag ``v{}.{}.{}``, so this value is ``{}``." .format(major, minor, rev, minor)) self.revision = CSRStatus( 8, reset=rev, description= "Revision git tag version. For example, this firmware was built from git tag ``v{}.{}.{}``, so this value is ``{}``." .format(major, minor, rev, rev)) self.gitrev = CSRStatus( 32, reset=gitrev, description= "First 32-bits of the git revision. This documentation was built from git rev ``{:08x}``, so this value is {}, which should be enough to check out the exact git version used to build this firmware." .format(gitrev, gitrev)) self.gitextra = CSRStatus( 10, reset=gitextra, description= "The number of additional commits beyond the git tag. For example, if this value is ``1``, then the repository this was built from has one additional commit beyond the tag indicated in `MAJOR`, `MINOR`, and `REVISION`." ) self.dirty = CSRStatus(fields=[ CSRField( "dirty", reset=dirty, description= "Set to ``1`` if this device was built from a git repo with uncommitted modifications." ) ]) model_val = None for model_check in models: try: if int(model_check[0]) == int(model): model_val = int(model_check[0]) except: pass try: if model_check[1] == model: model_val = int(model_check[0]) except: pass if model_val is None: raise ValueError("Model not found in `models` list!") self.model = CSRStatus(fields=[ CSRField( "model", reset=model_val, size=8, description= "Contains information on which model device this was built for.", values=models) ]) self.seed = CSRStatus( 32, reset=seed, description="32-bit seed used for the place-and-route.") self.comb += [ self.major.status.eq(major), self.minor.status.eq(minor), self.revision.status.eq(rev), self.gitrev.status.eq(gitrev), self.gitextra.status.eq(gitextra), self.dirty.fields.dirty.eq(dirty), self.model.fields.model.eq(model_val), self.seed.status.eq(seed), ]
def __init__(self, platform): self.intro = ModuleDoc( """Bitstream-patchable key ROM set for keys that are "baked in" to the FPGA image""" ) platform.toolchain.attr_translate["KEEP"] = ("KEEP", "TRUE") platform.toolchain.attr_translate["DONT_TOUCH"] = ("DONT_TOUCH", "TRUE") import binascii self.address = CSRStorage(8, name="address", description="address for ROM") self.data = CSRStatus(32, name="data", description="data from ROM") self.lockaddr = CSRStorage( 8, name="lockaddr", description= "address of the word to lock. Address locked on write, and cannot be undone." ) self.lockstat = CSRStatus( 1, name="lockstat", description= "If set, the requested address word is locked and will always return 0." ) lockmem = Memory(1, 256, init=[0] * 256) self.specials += lockmem self.specials.lockrd = lockmem.get_port(write_capable=False, mode=WRITE_FIRST, async_read=True) self.specials.lockwr = lockmem.get_port(write_capable=True, mode=WRITE_FIRST) self.comb += [ self.lockwr.adr.eq(self.lockaddr.storage), self.lockwr.dat_w.eq(1), self.lockwr.we.eq(self.lockaddr.re), ] rawdata = Signal(32) rng = SystemRandom() with open("rom.db", "w") as f: for bit in range(0, 32): lutsel = Signal(4) for lut in range(4): if lut == 0: lutname = 'A' elif lut == 1: lutname = 'B' elif lut == 2: lutname = 'C' else: lutname = 'D' romval = rng.getrandbits(64) # print("rom bit ", str(bit), lutname, ": ", binascii.hexlify(romval.to_bytes(8, byteorder='big'))) rom_name = "KEYROM" + str(bit) + lutname # X36Y99 and counting down if bit % 2 == 0: platform.toolchain.attr_translate[rom_name] = ( "LOC", "SLICE_X36Y" + str(50 + bit // 2)) else: platform.toolchain.attr_translate[rom_name] = ( "LOC", "SLICE_X37Y" + str(50 + bit // 2)) platform.toolchain.attr_translate[rom_name + 'BEL'] = ("BEL", lutname + '6LUT') platform.toolchain.attr_translate[rom_name + 'LOCK'] = ( "LOCK_PINS", "I5:A6, I4:A5, I3:A4, I2:A3, I1:A2, I0:A1") self.specials += [ Instance( "LUT6", name=rom_name, # p_INIT=0x0000000000000000000000000000000000000000000000000000000000000000, p_INIT=romval, i_I0=self.address.storage[0], i_I1=self.address.storage[1], i_I2=self.address.storage[2], i_I3=self.address.storage[3], i_I4=self.address.storage[4], i_I5=self.address.storage[5], o_O=lutsel[lut], attr=("KEEP", "DONT_TOUCH", rom_name, rom_name + 'BEL', rom_name + 'LOCK')) ] # record the ROM LUT locations in a DB and annotate the initial random value given f.write("KEYROM " + str(bit) + ' ' + lutname + ' ' + platform.toolchain.attr_translate[rom_name][1] + ' ' + str( binascii.hexlify( romval.to_bytes(8, byteorder='big'))) + '\n') self.comb += [ If(self.address.storage[6:] == 0, rawdata[bit].eq(lutsel[2])).Elif( self.address.storage[6:] == 1, rawdata[bit].eq(lutsel[3])).Elif( self.address.storage[6:] == 2, rawdata[bit].eq( lutsel[0])).Else(rawdata[bit].eq(lutsel[1])) ] allow_read = Signal() self.comb += [ allow_read.eq(~self.lockrd.dat_r), self.lockrd.adr.eq(self.address.storage), ] self.sync += [ If( allow_read, self.data.status.eq(rawdata), ).Else(self.data.status.eq(0), ), self.lockstat.status.eq(allow_read) ] platform.add_platform_command("create_pblock keyrom") platform.add_platform_command( 'resize_pblock [get_pblocks keyrom] -add ' + '{{SLICE_X36Y50:SLICE_X37Y65}}') #platform.add_platform_command("set_property CONTAIN_ROUTING true [get_pblocks keyrom]") # should be fine to mingle the routing for this pblock platform.add_platform_command( "add_cells_to_pblock [get_pblocks keyrom] [get_cells KEYROM*]")