diff --git a/amaranth_orchard/base/__init__.py b/amaranth_orchard/base/__init__.py index e69de29..578865e 100644 --- a/amaranth_orchard/base/__init__.py +++ b/amaranth_orchard/base/__init__.py @@ -0,0 +1,2 @@ +from .platform_timer import * +from .soc_id import * diff --git a/amaranth_orchard/base/gpio.py b/amaranth_orchard/base/gpio.py deleted file mode 100644 index 4910f9d..0000000 --- a/amaranth_orchard/base/gpio.py +++ /dev/null @@ -1,84 +0,0 @@ -from amaranth import * -from amaranth.lib import wiring -from amaranth.lib.wiring import In, Out, flipped, connect - -from amaranth_soc import csr - - -__all__ = ["GPIOPins", "GPIOPeripheral"] - - -class GPIOPins(wiring.PureInterface): - class Signature(wiring.Signature): - def __init__(self, width): - self._width = width - super().__init__({ - "o": Out(unsigned(width)), - "oe": Out(unsigned(width)), - "i": In(unsigned(width)), - }) - - @property - def width(self): - return self._width - - def create(self, *, path=(), src_loc_at=0): - return GPIOPins(width=self.width, path=path, src_loc_at=1 + src_loc_at) - - def __init__(self, width, *, path=(), src_loc_at=0): - super().__init__(self.Signature(width), path=path, src_loc_at=1 + src_loc_at) - - -class GPIOPeripheral(wiring.Component): - class DO(csr.Register, access="rw"): - """output data (R/W, ignored for pins configured as inputs)""" - def __init__(self, width): - super().__init__({"pins": csr.Field(csr.action.RW, unsigned(width))}) - - class OE(csr.Register, access="rw"): - """output enable (R/W) 1=output, 0=input""" - def __init__(self, width): - super().__init__({"pins": csr.Field(csr.action.RW, unsigned(width))}) - - class DI(csr.Register, access="r"): - """input data (R)""" - def __init__(self, width): - super().__init__({"pins": csr.Field(csr.action.R, unsigned(width))}) - - """Simple GPIO peripheral. - - All pins default to input at power up. - """ - def __init__(self, *, pins): - if len(pins.o) > 32: - raise ValueError(f"Pin width must be lesser than or equal to 32, not {len(pins.o)}") - - self.width = len(pins.o) - self.pins = pins - - regs = csr.Builder(addr_width=4, data_width=8) - - self._do = regs.add("do", self.DO(self.width), offset=0x0) - self._oe = regs.add("oe", self.OE(self.width), offset=0x4) - self._di = regs.add("di", self.DI(self.width), offset=0x8) - - self._bridge = csr.Bridge(regs.as_memory_map()) - - super().__init__({ - "bus": In(csr.Signature(addr_width=regs.addr_width, data_width=regs.data_width)), - }) - self.bus.memory_map = self._bridge.bus.memory_map - - def elaborate(self, platform): - m = Module() - m.submodules.bridge = self._bridge - - connect(m, flipped(self.bus), self._bridge.bus) - - m.d.comb += [ - self.pins.o .eq(self._do.f.pins.data), - self.pins.oe.eq(self._oe.f.pins.data), - ] - m.d.sync += self._di.f.pins.r_data.eq(self.pins.i) - - return m diff --git a/amaranth_orchard/io/__init__.py b/amaranth_orchard/io/__init__.py index e69de29..c867353 100644 --- a/amaranth_orchard/io/__init__.py +++ b/amaranth_orchard/io/__init__.py @@ -0,0 +1,2 @@ +from .gpio import * +from .uart import * diff --git a/amaranth_orchard/io/gpio.py b/amaranth_orchard/io/gpio.py new file mode 100644 index 0000000..5afad19 --- /dev/null +++ b/amaranth_orchard/io/gpio.py @@ -0,0 +1,92 @@ +from amaranth import Module, unsigned +from amaranth.lib import wiring +from amaranth.lib.wiring import In, Out, flipped, connect + +from amaranth_soc import csr, gpio + +from chipflow_lib.platforms import BidirPinSignature, PinSignature + +__all__ = ["GPIOPeripheral"] + + +class GPIOPeripheral(wiring.Component): + + class Signature(wiring.Signature): + def __init__(self, pin_count=1): + if pin_count > 32: + raise ValueError(f"Pin pin_count must be lesser than or equal to 32, not {pin_count}") + self._pin_count = pin_count + super().__init__({ + "gpio": Out(BidirPinSignature(pin_count, all_have_oe=True)) + }) + + @property + def pin_count(self): + return self._pin_count + + """Wrapper for amaranth_soc gpio with chipflow_lib.PinSignature support + + Parameters + ---------- + pin_count : :class:`int` + Number of GPIO pins. + addr_width : :class:`int` + CSR bus address width. Defaults to ``4``. + data_width : :class:`int` + CSR bus data width. Defaults to ``8``. + input_stages : :class:`int` + Number of synchronization stages between pin inputs and the :class:`~Peripheral.Input` + register. Optional. Defaults to ``2``. + + Attributes + ---------- + bus : :class:`csr.Interface` + CSR bus interface providing access to registers. + pins : :class:`list` of :class:`wiring.PureInterface` of :class:`PinSignature` + GPIO pin interfaces. + alt_mode : :class:`Signal` + Indicates which members of the :attr:`Peripheral.pins` array are in alternate mode. + + Raises + ------ + :exc:`TypeError` + If ``pin_count`` is not a positive integer. + :exc:`TypeError` + If ``input_stages`` is not a non-negative integer. + """ + + def __init__(self, *, pin_count, addr_width=4, data_width=8, input_stages=2): + self._gpio = gpio.Peripheral(pin_count=pin_count, + addr_width=addr_width, + data_width=data_width, + input_stages=input_stages) + + super().__init__({ + "bus": In(csr.Signature(addr_width=addr_width, data_width=data_width)), + "pins": Out(self.Signature(pin_count)), + "alt_mode": Out(unsigned(pin_count)), + }) + self.bus.memory_map = self._gpio.bus.memory_map + + def elaborate(self, platform): + m = Module() + m.submodules._gpio = gpio = self._gpio + + connect(m, flipped(self.bus), self._gpio.bus) + for i in range(self._gpio.pin_count): + # m.d.comb += self.pins.gpio.i[i].eq(self._gpio.pins[i].i) + # m.d.comb += self._gpio.pins[i].o.eq(self.pins.gpio.o[i]) + # m.d.comb += self._gpio.pins[i].oe.eq(self.pins.gpio.oe[i]) + m.d.comb += self._gpio.pins[i].i.eq(self.pins.gpio.i[i]) + m.d.comb += self.pins.gpio.o[i].eq(self._gpio.pins[i].o) + m.d.comb += self.pins.gpio.oe[i].eq(self._gpio.pins[i].oe) + + return m + + @property + def pin_count(self): + return self._gpio.pin_count + + @property + def input_stages(self): + return self._gpio.input_stages diff --git a/amaranth_orchard/io/uart.py b/amaranth_orchard/io/uart.py index dec89d9..789d9a8 100644 --- a/amaranth_orchard/io/uart.py +++ b/amaranth_orchard/io/uart.py @@ -1,30 +1,27 @@ -from amaranth import * +from amaranth import Module, Signal, unsigned from amaranth.lib import wiring from amaranth.lib.wiring import In, Out, flipped, connect from amaranth_soc import csr from amaranth_stdio.serial import AsyncSerialRX, AsyncSerialTX +from chipflow_lib.platforms import OutputPinSignature, InputPinSignature -__all__ = ["UARTPins", "UARTPeripheral"] +__all__ = ["UARTPeripheral"] -class UARTPins(wiring.PureInterface): + +class UARTPeripheral(wiring.Component): class Signature(wiring.Signature): def __init__(self): super().__init__({ - "tx_o": Out(1), - "rx_i": In(1), + "tx": Out(OutputPinSignature(1)), + "rx": Out(InputPinSignature(1)), }) - def create(self, *, path=(), src_loc_at=0): - return UARTPins(path=path, src_loc_at=1 + src_loc_at) - def __init__(self, *, path=(), src_loc_at=0): super().__init__(self.Signature(), path=path, src_loc_at=1 + src_loc_at) - -class UARTPeripheral(wiring.Component): class TxData(csr.Register, access="w"): """valid to write to when tx_rdy is 1, will trigger a transmit""" val: csr.Field(csr.action.W, unsigned(8)) @@ -53,22 +50,22 @@ def __init__(self, init_divisor): TODO: Interrupts support, perhaps mimic something with upstream Linux kernel support... """ - def __init__(self, *, init_divisor, pins): + def __init__(self, *, init_divisor): self.init_divisor = init_divisor - self.pins = pins regs = csr.Builder(addr_width=5, data_width=8) - self._tx_data = regs.add("tx_data", self.TxData(), offset=0x00) - self._rx_data = regs.add("rx_data", self.RxData(), offset=0x04) - self._tx_rdy = regs.add("tx_rdy", self.TxReady(), offset=0x08) + self._tx_data = regs.add("tx_data", self.TxData(), offset=0x00) + self._rx_data = regs.add("rx_data", self.RxData(), offset=0x04) + self._tx_rdy = regs.add("tx_rdy", self.TxReady(), offset=0x08) self._rx_avail = regs.add("rx_avail", self.RxAvail(), offset=0x0c) - self._divisor = regs.add("divisor", self.Divisor(init_divisor), offset=0x10) + self._divisor = regs.add("divisor", self.Divisor(init_divisor), offset=0x10) self._bridge = csr.Bridge(regs.as_memory_map()) super().__init__({ "bus": In(csr.Signature(addr_width=regs.addr_width, data_width=regs.data_width)), + "pins": Out(self.Signature()), }) self.bus.memory_map = self._bridge.bus.memory_map @@ -80,7 +77,7 @@ def elaborate(self, platform): m.submodules.tx = tx = AsyncSerialTX(divisor=self.init_divisor, divisor_bits=24) m.d.comb += [ - self.pins.tx_o.eq(tx.o), + self.pins.tx.o.eq(tx.o), tx.data.eq(self._tx_data.f.val.w_data), tx.ack.eq(self._tx_data.f.val.w_stb), self._tx_rdy.f.val.r_data.eq(tx.rdy), @@ -102,7 +99,7 @@ def elaborate(self, platform): ] m.d.comb += [ - rx.i.eq(self.pins.rx_i), + rx.i.eq(self.pins.rx.i), rx.ack.eq(~rx_avail), rx.divisor.eq(self._divisor.f.val.data), self._rx_data.f.val.r_data.eq(rx_buf), diff --git a/amaranth_orchard/memory/__init__.py b/amaranth_orchard/memory/__init__.py index e69de29..67fdde8 100644 --- a/amaranth_orchard/memory/__init__.py +++ b/amaranth_orchard/memory/__init__.py @@ -0,0 +1,3 @@ +from .hyperram import * # noqa +from .spimemio import * # noqa +from .sram import * # noqa diff --git a/amaranth_orchard/memory/hyperram.py b/amaranth_orchard/memory/hyperram.py index 61b3d25..a2eae17 100644 --- a/amaranth_orchard/memory/hyperram.py +++ b/amaranth_orchard/memory/hyperram.py @@ -5,44 +5,30 @@ # Copyright (c) 2021-2022 gatecat # SPDX-License-Identifier: BSD-2-Clause -from amaranth import * +from amaranth import Module, ClockSignal, ResetSignal, Signal, unsigned, ClockDomain, Cat from amaranth.lib import wiring from amaranth.lib.wiring import In, Out, connect, flipped from amaranth.utils import ceil_log2 -from amaranth.sim import * - from amaranth_soc import csr, wishbone from amaranth_soc.memory import MemoryMap +from chipflow_lib.platforms import BidirPinSignature, OutputPinSignature -__all__ = ["HyperRAMPins", "HyperRAM"] +__all__ = ["HyperRAM"] -class HyperRAMPins(wiring.PureInterface): +class HyperRAM(wiring.Component): class Signature(wiring.Signature): def __init__(self, *, cs_count=1): - self.cs_count = cs_count super().__init__({ - "clk_o": Out(1), - "csn_o": Out(cs_count), - "rstn_o": Out(1), - "rwds_o": Out(1), - "rwds_oe": Out(1), - "rwds_i": In(1), - "dq_o": Out(8), - "dq_oe": Out(8), - "dq_i": In(8), + "clk": Out(OutputPinSignature(1)), + "csn": Out(OutputPinSignature(cs_count)), + "rstn": Out(OutputPinSignature(1)), + "rwds": Out(BidirPinSignature(1)), + "dq": Out(BidirPinSignature(8)), }) - def create(self, *, path=(), src_loc_at=0): - return HyperRAMPins(cs_count=self.cs_count, src_loc_at=1 + src_loc_at) - - def __init__(self, *, cs_count=1, path=(), src_loc_at=0): - super().__init__(self.Signature(cs_count=cs_count), path=path, src_loc_at=1 + src_loc_at) - - -class HyperRAM(wiring.Component): class CtrlConfig(csr.Register, access="rw"): def __init__(self, init_latency): super().__init__({ @@ -60,16 +46,15 @@ class HRAMConfig(csr.Register, access="w"): This core favors portability and ease of use over performance. """ - def __init__(self, mem_name=("mem",), *, pins, init_latency=7): - self.pins = pins - self.cs_count = len(self.pins.csn_o) + def __init__(self, mem_name=("mem",), *, cs_count=1, init_latency=7): + self.cs_count = cs_count self.size = 2**23 * self.cs_count # 8MB per CS pin self.init_latency = init_latency assert self.init_latency in (6, 7) # TODO: anything else possible ? regs = csr.Builder(addr_width=3, data_width=8) - self._ctrl_cfg = regs.add("ctrl_cfg", self.CtrlConfig(), offset=0x0) + self._ctrl_cfg = regs.add("ctrl_cfg", self.CtrlConfig(init_latency), offset=0x0) self._hram_cfg = regs.add("hram_cfg", self.HRAMConfig(), offset=0x4) self._bridge = csr.Bridge(regs.as_memory_map()) @@ -79,9 +64,10 @@ def __init__(self, mem_name=("mem",), *, pins, init_latency=7): data_memory_map.add_resource(name=mem_name, size=self.size, resource=self) super().__init__({ - "ctrl_bus": csr.Signature(addr_width=regs.addr_width, data_width=regs.data_width), - "data_bus": wishbone.Signature(addr_width=ceil_log2(self.size / 4), data_width=32, - granularity=8), + "ctrl_bus": In(csr.Signature(addr_width=regs.addr_width, data_width=regs.data_width)), + "data_bus": In(wishbone.Signature(addr_width=ceil_log2(self.size >> 2), data_width=32, + granularity=8)), + "pins": Out(self.Signature()), }) self.ctrl_bus.memory_map = ctrl_memory_map self.data_bus.memory_map = data_memory_map @@ -90,7 +76,7 @@ def elaborate(self, platform): m = Module() m.submodules.bridge = self._bridge - connect(m, flipped(self.bus), self._bridge.bus) + connect(m, flipped(self.ctrl_bus), self._bridge.bus) is_ctrl_write = Signal() latched_adr = Signal(len(self.data_bus.adr)) @@ -104,7 +90,6 @@ def elaborate(self, platform): # Data shift register sr = Signal(48) - sr_shift = Signal() # Whether or not we need to apply x2 latency x2_lat = Signal() @@ -123,13 +108,13 @@ def elaborate(self, platform): m.d.sync += counter.eq(counter-1) with m.If(counter.any()): # move shift register (sample/output data) on posedge - m.d.sync += sr.eq(Cat(self.pins.dq_i, sr[:-8])) + m.d.sync += sr.eq(Cat(self.pins.dq.i, sr[:-8])) m.d.comb += [ - self.pins.clk_o.eq(clk), - self.pins.csn_o.eq(csn), - self.pins.rstn_o.eq(~ResetSignal()), - self.pins.dq_o.eq(sr[-8:]), + self.pins.clk.o.eq(clk), + self.pins.csn.o.eq(csn), + self.pins.rstn.o.eq(~ResetSignal()), + self.pins.dq.o.eq(sr[-8:]), self.data_bus.dat_r.eq(sr[:32]), ] @@ -137,41 +122,41 @@ def elaborate(self, platform): with m.State("IDLE"): m.d.sync += [ counter.eq(0), - self.pins.rwds_oe.eq(0), + self.pins.rwds.oe.eq(0), csn.eq((1 << self.cs_count) - 1), # all disabled ] with m.If(self.data_bus.stb & self.data_bus.cyc): # data bus activity m.d.sync += [ csn.eq(~(1 << (self.data_bus.adr[21:]))), - self.pins.dq_oe.eq(1), + self.pins.dq.oe.eq(1), counter.eq(6), # Assign CA - sr[47].eq(~self.data_bus.we), # R/W# - sr[46].eq(0), # memory space - sr[45].eq(1), # linear burst - sr[16:45].eq(self.data_bus.adr[2:21]), # upper address - sr[4:16].eq(0), # RFU - sr[1:3].eq(self.data_bus.adr[0:2]), # lower address - sr[0].eq(0), # address LSB (0 for 32-bit xfers) + sr[47].eq(~self.data_bus.we), # R/W# + sr[46].eq(0), # memory space + sr[45].eq(1), # linear burst + sr[16:45].eq(self.data_bus.adr[2:21]), # upper address + sr[4:16].eq(0), # RFU + sr[1:3].eq(self.data_bus.adr[0:2]), # lower address + sr[0].eq(0), # address LSB (0 for 32-bit xfers) latched_adr.eq(self.data_bus.adr), latched_we.eq(self.data_bus.we), is_ctrl_write.eq(0), ] m.next = "WAIT_CA" - with m.If(self._hram_cfg.f.val.w_stb): # config register write + with m.If(self._hram_cfg.f.val.w_stb): # config register write m.d.sync += [ csn.eq(~(1 << (self._hram_cfg.f.val.w_data[16:16+ceil_log2(self.cs_count)]))), - self.pins.dq_oe.eq(1), + self.pins.dq.oe.eq(1), counter.eq(6), # Assign CA - sr[47].eq(0), # R/W# - sr[46].eq(1), # memory space - sr[45].eq(1), # linear burst - sr[24:45].eq(1), # upper address - sr[16:24].eq(0), # - sr[4:16].eq(0), # RFU - sr[1:3].eq(0), # lower address - sr[0].eq(0), # address LSB (0 for 32-bit xfers) + sr[47].eq(0), # R/W# + sr[46].eq(1), # memory space + sr[45].eq(1), # linear burst + sr[24:45].eq(1), # upper address + sr[16:24].eq(0), # + sr[4:16].eq(0), # RFU + sr[1:3].eq(0), # lower address + sr[0].eq(0), # address LSB (0 for 32-bit xfers) latched_cfg.eq(self._hram_cfg.f.val.w_data[0:16]), is_ctrl_write.eq(1), ] @@ -181,7 +166,7 @@ def elaborate(self, platform): with m.If(counter == 3): # RWDS tells us if we need 2x latency or not # sample at an arbitrary midpoint in CA - m.d.sync += x2_lat.eq(self.pins.rwds_i) + m.d.sync += x2_lat.eq(self.pins.rwds.i) with m.If(counter == 1): # (almost) done shifting CA with m.If(is_ctrl_write): @@ -199,31 +184,31 @@ def elaborate(self, platform): m.d.sync += counter.eq(2*self._ctrl_cfg.f.latency.data - 2) m.next = "WAIT_LAT" with m.State("WAIT_LAT"): - m.d.sync += self.pins.dq_oe.eq(0) + m.d.sync += self.pins.dq.oe.eq(0) with m.If(counter == 1): # About to shift data m.d.sync += [ sr[:16].eq(0), sr[16:].eq(self.data_bus.dat_w), - self.pins.dq_oe.eq(self.data_bus.we), - self.pins.rwds_oe.eq(self.data_bus.we), - self.pins.rwds_o.eq(~self.data_bus.sel[3]), + self.pins.dq.oe.eq(self.data_bus.we), + self.pins.rwds.oe.eq(self.data_bus.we), + self.pins.rwds.o.eq(~self.data_bus.sel[3]), counter.eq(4), ] m.next = "SHIFT_DAT" with m.State("SHIFT_DAT"): with m.If(counter == 4): - m.d.sync += self.pins.rwds_o.eq(~self.data_bus.sel[2]) + m.d.sync += self.pins.rwds.o.eq(~self.data_bus.sel[2]) with m.If(counter == 3): - m.d.sync += self.pins.rwds_o.eq(~self.data_bus.sel[1]) + m.d.sync += self.pins.rwds.o.eq(~self.data_bus.sel[1]) with m.If(counter == 2): - m.d.sync += self.pins.rwds_o.eq(~self.data_bus.sel[0]) + m.d.sync += self.pins.rwds.o.eq(~self.data_bus.sel[0]) with m.If(counter == 1): m.next = "ACK_XFER" with m.State("ACK_XFER"): m.d.sync += [ - self.pins.rwds_oe.eq(0), - self.pins.dq_oe.eq(0), + self.pins.rwds.oe.eq(0), + self.pins.dq.oe.eq(0), self.data_bus.ack.eq(1), wait_count.eq(9) ] @@ -239,9 +224,9 @@ def elaborate(self, platform): m.d.sync += [ sr[:16].eq(0), sr[16:].eq(self.data_bus.dat_w), - self.pins.dq_oe.eq(self.data_bus.we), - self.pins.rwds_oe.eq(self.data_bus.we), - self.pins.rwds_o.eq(~self.data_bus.sel[3]), + self.pins.dq.oe.eq(self.data_bus.we), + self.pins.rwds.oe.eq(self.data_bus.we), + self.pins.rwds.o.eq(~self.data_bus.sel[3]), latched_adr.eq(self.data_bus.adr), counter.eq(4), ] @@ -258,57 +243,9 @@ def elaborate(self, platform): m.next = "CTRL_DONE" with m.State("CTRL_DONE"): m.d.sync += [ - self.pins.rwds_oe.eq(0), - self.pins.dq_oe.eq(0), + self.pins.rwds.oe.eq(0), + self.pins.dq.oe.eq(0), csn.eq((1 << self.cs_count) - 1), ] m.next = "IDLE" return m - -def sim(): - pins = HyperRAMPins(cs_count=1) - m = Module() - m.submodules.hram = hram = HyperRAM(pins=pins) - sim = Simulator(m) - sim.add_clock(1e-6) - def process(): - yield hram.data_bus.adr.eq(0x5A5A5A) - yield hram.data_bus.dat_w.eq(0xF0F0F0F0) - yield hram.data_bus.sel.eq(1) - yield hram.data_bus.we.eq(1) - yield hram.data_bus.stb.eq(1) - yield hram.data_bus.cyc.eq(1) - for i in range(100): - if (yield hram.data_bus.ack): - yield hram.data_bus.stb.eq(0) - yield hram.data_bus.cyc.eq(0) - yield - yield hram.data_bus.adr.eq(0x5A5A5A) - yield hram.data_bus.sel.eq(1) - yield hram.data_bus.we.eq(0) - yield hram.data_bus.stb.eq(1) - yield hram.data_bus.cyc.eq(1) - yield pins.rwds_i.eq(1) - yield pins.dq_i.eq(0xFF) - for i in range(100): - if (yield hram.data_bus.ack): - yield hram.data_bus.stb.eq(0) - yield hram.data_bus.cyc.eq(0) - yield - yield hram.ctrl_bus.adr.eq(1) - yield hram.ctrl_bus.dat_w.eq(0x55AA) - yield hram.ctrl_bus.sel.eq(0xF) - yield hram.ctrl_bus.we.eq(1) - yield hram.ctrl_bus.stb.eq(1) - yield hram.ctrl_bus.cyc.eq(1) - for i in range(100): - if (yield hram.ctrl_bus.ack): - yield hram.ctrl_bus.stb.eq(0) - yield hram.ctrl_bus.cyc.eq(0) - yield - sim.add_sync_process(process) - with sim.write_vcd("hyperram.vcd", "hyperram.gtkw"): - sim.run() - -if __name__ == '__main__': - sim() diff --git a/amaranth_orchard/memory/spimemio.py b/amaranth_orchard/memory/spimemio.py index 9731f58..bd673ed 100644 --- a/amaranth_orchard/memory/spimemio.py +++ b/amaranth_orchard/memory/spimemio.py @@ -7,29 +7,20 @@ from amaranth_soc import csr, wishbone from amaranth_soc.memory import MemoryMap +from chipflow_lib.platforms import BidirPinSignature,OutputPinSignature -__all__ = ["QSPIPins", "SPIMemIO"] +__all__ = ["SPIMemIO"] -class QSPIPins(wiring.PureInterface): +class SPIMemIO(wiring.Component): class Signature(wiring.Signature): def __init__(self): super().__init__({ - "clk_o": Out(1), - "csn_o": Out(1), - "d_o": Out(4), - "d_oe": Out(4), - "d_i": In(4), + "clk": Out(OutputPinSignature(1)), + "csn": Out(OutputPinSignature(1)), + "d": Out(BidirPinSignature(4, all_have_oe=True)), }) - def create(self, *, path=(), src_loc_at=0): - return QSPIPins(path=path, src_loc_at=1 + src_loc_at) - - def __init__(self, *, path=(), src_loc_at=0): - super().__init__(self.Signature(), path=path, src_loc_at=1 + src_loc_at) - - -class SPIMemIO(wiring.Component): class _ControlBridge(wiring.Component): bus: In(csr.Signature(addr_width=exact_log2(4), data_width=8)) cfgreg_we: Out(unsigned(4)) @@ -57,12 +48,12 @@ def elaborate(self, platform): - data_bus is a bus peripheral that directly maps the 16MB of read-only flash memory. """ - def __init__(self, mem_name=("mem",), cfg_name=("cfg",), *, flash): - self.flash = flash - self.size = 2**24 + def __init__(self, *, mem_name=("mem",), cfg_name=("cfg",)): + self.size = 2**24 size_words = (self.size * 8) // 32 super().__init__({ + "pins": Out(self.Signature()), "ctrl_bus": In(csr.Signature(addr_width=exact_log2(4), data_width=8)), "data_bus": In(wishbone.Signature(addr_width=exact_log2(size_words), data_width=32, granularity=8)), @@ -85,32 +76,28 @@ def elaborate(self, platform): spi_ready = Signal() # TODO : QSPI - m.submodules.spimemio = Instance( - "spimemio", - i_clk=ClockSignal(), - i_resetn=~ResetSignal(), - i_valid=self.data_bus.cyc & self.data_bus.stb, - o_ready=spi_ready, - i_addr=Cat(Const(0, 2), self.data_bus.adr), # Hack to force a 1MB offset - o_rdata=self.data_bus.dat_r, - o_flash_csb=self.flash.csn_o, - o_flash_clk=self.flash.clk_o, - o_flash_io0_oe=self.flash.d_oe[0], - o_flash_io1_oe=self.flash.d_oe[1], - o_flash_io2_oe=self.flash.d_oe[2], - o_flash_io3_oe=self.flash.d_oe[3], - o_flash_io0_do=self.flash.d_o[0], - o_flash_io1_do=self.flash.d_o[1], - o_flash_io2_do=self.flash.d_o[2], - o_flash_io3_do=self.flash.d_o[3], - i_flash_io0_di=self.flash.d_i[0], - i_flash_io1_di=self.flash.d_i[1], - i_flash_io2_di=self.flash.d_i[2], - i_flash_io3_di=self.flash.d_i[3], - i_cfgreg_we=ctrl_bridge.cfgreg_we, - i_cfgreg_di=ctrl_bridge.cfgreg_di, - o_cfgreg_do=ctrl_bridge.cfgreg_do, - ) + verilog_map = { + "i_clk": ClockSignal(), + "i_resetn": ~ResetSignal(), + "i_valid": self.data_bus.cyc & self.data_bus.stb, + "o_ready": spi_ready, + "i_addr": Cat(Const(0, 2), self.data_bus.adr), # Hack to force a 1MB offset + "o_rdata": self.data_bus.dat_r, + "o_flash_csb": self.pins.csn.o, + "o_flash_clk": self.pins.clk.o, + "i_cfgreg_we": ctrl_bridge.cfgreg_we, + "i_cfgreg_di": ctrl_bridge.cfgreg_di, + "o_cfgreg_do": ctrl_bridge.cfgreg_do, + } | { + f"o_flash_io{n}_oe": self.pins.d.oe[n] for n in range(4) + } | { + f"o_flash_io{n}_do": self.pins.d.o[n] for n in range(4) + } | { + f"i_flash_io{n}_di": self.pins.d.i[n] for n in range(4) + } + + m.submodules.spimemio = Instance("spimemio", **verilog_map) + # From https://github.com/im-tomu/foboot/blob/master/hw/rtl/picorvspi.py read_active = Signal() with m.If(self.data_bus.stb & self.data_bus.cyc & ~read_active): @@ -123,7 +110,7 @@ def elaborate(self, platform): m.d.sync += self.data_bus.ack.eq(0) if platform is not None: - path = Path(__file__).parent / f"verilog/spimemio.v" + path = Path(__file__).parent / "verilog/spimemio.v" with open(path, 'r') as f: platform.add_file(path.name, f) diff --git a/pyproject.toml b/pyproject.toml index 4b9e5f8..240704a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,9 +13,12 @@ authors = [ readme = {file = "README.md", content-type = "text/markdown"} license = {file = "LICENSE.txt"} -requires-python = "~=3.8" +requires-python = ">=3.10" dependencies = [ - "amaranth>=0.5,<0.7", + "amaranth>=0.5,<0.6", + "chipflow-lib @ git+https://github.com/ChipFlow/chipflow-lib.git", + "amaranth-soc @ git+https://github.com/amaranth-lang/amaranth-soc", + "amaranth-stdio @ git+https://github.com/amaranth-lang/amaranth-stdio", ] # Build system configuration @@ -25,3 +28,13 @@ requires = ["pdm-backend"] build-backend = "pdm.backend" # Development workflow configuration + +[tool.pyright] +diagnosticMode=false +typeCheckingMode = "off" +reportInvalidTypeForm = false +reportMissingImports = false +reportUnboundVariable = false + +[tool.ruff.lint] +ignore = ['F403', 'F405'] diff --git a/tests/test_gpio.py b/tests/test_gpio.py new file mode 100644 index 0000000..fab03ae --- /dev/null +++ b/tests/test_gpio.py @@ -0,0 +1,330 @@ +# amaranth: UnusedElaboratable=no + +import unittest +from amaranth import * +from amaranth.sim import * + +from amaranth_orchard.io import GPIOPeripheral + + +class PeripheralTestCase(unittest.TestCase): + def test_init(self): + dut_1 = GPIOPeripheral(pin_count=4, addr_width=2, data_width=8) + self.assertEqual(dut_1.pin_count, 4) + self.assertEqual(dut_1.input_stages, 2) + self.assertEqual(dut_1.bus.addr_width, 2) + self.assertEqual(dut_1.bus.data_width, 8) + dut_2 = GPIOPeripheral(pin_count=1, addr_width=8, data_width=16, input_stages=3) + self.assertEqual(dut_2.pin_count, 1) + self.assertEqual(dut_2.input_stages, 3) + self.assertEqual(dut_2.bus.addr_width, 8) + self.assertEqual(dut_2.bus.data_width, 16) + + def test_init_wrong_pin_count(self): + with self.assertRaisesRegex(TypeError, + r"Pin count must be a positive integer, not 'foo'"): + GPIOPeripheral(pin_count="foo", addr_width=2, data_width=8) + with self.assertRaisesRegex(TypeError, + r"Pin count must be a positive integer, not 0"): + GPIOPeripheral(pin_count=0, addr_width=2, data_width=8) + + def test_init_wrong_input_stages(self): + with self.assertRaisesRegex(TypeError, + r"Input stages must be a non-negative integer, not 'foo'"): + GPIOPeripheral(pin_count=1, addr_width=2, data_width=8, input_stages="foo") + with self.assertRaisesRegex(TypeError, + r"Input stages must be a non-negative integer, not -1"): + GPIOPeripheral(pin_count=1, addr_width=2, data_width=8, input_stages=-1) + + async def _csr_access(self, ctx, dut, addr, r_stb=0, w_stb=0, w_data=0, r_data=0): + ctx.set(dut.bus.addr, addr) + ctx.set(dut.bus.r_stb, r_stb) + ctx.set(dut.bus.w_stb, w_stb) + ctx.set(dut.bus.w_data, w_data) + await ctx.tick() + if r_stb: + self.assertEqual(ctx.get(dut.bus.r_data), r_data) + ctx.set(dut.bus.r_stb, 0) + ctx.set(dut.bus.w_stb, 0) + + def test_sim(self): + dut = GPIOPeripheral(pin_count=4, addr_width=2, data_width=8) + + mode_addr = 0x0 + input_addr = 0x1 + output_addr = 0x2 + setclr_addr = 0x3 + + async def testbench(ctx): + # INPUT_ONLY mode ===================================================================== + + # - read Mode: + await self._csr_access(ctx, dut, mode_addr, r_stb=1, r_data=0b00000000) + self.assertEqual(ctx.get(dut.alt_mode), 0b0000) + self.assertEqual(ctx.get(dut.pins.gpio.oe), 0b0000) + self.assertEqual(ctx.get(dut.pins.gpio.o), 0b0000) + + # - read Input: + ctx.set(dut.pins.gpio.i[1], 1) + ctx.set(dut.pins.gpio.i[3], 1) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) + ctx.set(dut.pins.gpio.i[1], 0) + ctx.set(dut.pins.gpio.i[3], 0) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0xa) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) + + # - write 0xf to Output: + await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0x0, w_stb=1, w_data=0xf) + await ctx.tick() + self.assertEqual(ctx.get(dut.pins.gpio.oe), 0b0000) + self.assertEqual(ctx.get(dut.pins.gpio.o), 0b1111) + + # - write 0x22 to SetClr (clear pins[0] and pins[2]): + await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x22) + await ctx.tick() + self.assertEqual(ctx.get(dut.pins.gpio.oe), 0b0000) + self.assertEqual(ctx.get(dut.pins.gpio.o), 0b1010) + + # - write 0x0 to Output: + await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) + await ctx.tick() + self.assertEqual(ctx.get(dut.pins.gpio.oe), 0b0000) + self.assertEqual(ctx.get(dut.pins.gpio.o), 0b0000) + + # - write 0x44 to SetClr (set pins[1] and pins[3]): + await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x44) + await ctx.tick() + self.assertEqual(ctx.get(dut.pins.gpio.oe), 0b0000) + self.assertEqual(ctx.get(dut.pins.gpio.o), 0b1010) + + # - write 0x0 to Output: + await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) + await ctx.tick() + self.assertEqual(ctx.get(dut.pins.gpio.oe), 0b0000) + self.assertEqual(ctx.get(dut.pins.gpio.o), 0b0000) + + # - write 0xff to SetClr (no-op): + await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0xff) + await ctx.tick() + self.assertEqual(ctx.get(dut.pins.gpio.oe), 0b0000) + self.assertEqual(ctx.get(dut.pins.gpio.o), 0b0000) + + # PUSH_PULL mode ====================================================================== + + # - write Mode: + await self._csr_access(ctx, dut, mode_addr, w_stb=1, w_data=0b01010101) + await ctx.tick() + self.assertEqual(ctx.get(dut.alt_mode), 0b0000) + self.assertEqual(ctx.get(dut.pins.gpio.oe), 0b1111) + self.assertEqual(ctx.get(dut.pins.gpio.o), 0b0000) + + # - read Input: + ctx.set(dut.pins.gpio.i[1], 1) + ctx.set(dut.pins.gpio.i[3], 1) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) + ctx.set(dut.pins.gpio.i[1], 0) + ctx.set(dut.pins.gpio.i[3], 0) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0xa) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) + + # - write 0xf to Output: + await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0x0, w_stb=1, w_data=0xf) + await ctx.tick() + self.assertEqual(ctx.get(dut.pins.gpio.oe), 0b1111) + self.assertEqual(ctx.get(dut.pins.gpio.o), 0b1111) + + # - write 0x22 to SetClr (clear pins[0] and pins[2]): + await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x22) + await ctx.tick() + self.assertEqual(ctx.get(dut.pins.gpio.oe), 0b1111) + self.assertEqual(ctx.get(dut.pins.gpio.o), 0b1010) + + # - write 0x0 to Output: + await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) + await ctx.tick() + self.assertEqual(ctx.get(dut.pins.gpio.oe), 0b1111) + self.assertEqual(ctx.get(dut.pins.gpio.o), 0b0000) + + # - write 0x44 to SetClr (set pins[1] and pins[3]): + await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x44) + await ctx.tick() + self.assertEqual(ctx.get(dut.pins.gpio.oe), 0b1111) + self.assertEqual(ctx.get(dut.pins.gpio.o), 0b1010) + + """ + # - write 0x0 to Output: + await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) + await ctx.tick() + for n in range(4): + self.assertEqual(ctx.get(dut.pins[n].oe), 1) + self.assertEqual(ctx.get(dut.pins[n].o), 0) + + # - write 0xff to SetClr (no-op): + await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0xff) + await ctx.tick() + for n in range(4): + self.assertEqual(ctx.get(dut.pins[n].oe), 1) + self.assertEqual(ctx.get(dut.pins[n].o), 0) + + # OPEN_DRAIN mode ===================================================================== + + # - write Mode: + await self._csr_access(ctx, dut, mode_addr, w_stb=1, w_data=0b10101010) + await ctx.tick() + for n in range(4): + self.assertEqual(ctx.get(dut.alt_mode[n]), 0) + self.assertEqual(ctx.get(dut.pins[n].oe), 1) + self.assertEqual(ctx.get(dut.pins[n].o), 0) + + # - read Input: + ctx.set(dut.pins[1].i, 1) + ctx.set(dut.pins[3].i, 1) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) + ctx.set(dut.pins[1].i, 0) + ctx.set(dut.pins[3].i, 0) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0xa) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) + + # - write 0xf to Output: + await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0x0, w_stb=1, w_data=0xf) + await ctx.tick() + for n in range(4): + self.assertEqual(ctx.get(dut.pins[n].oe), 0) + self.assertEqual(ctx.get(dut.pins[n].o), 0) + + # - write 0x22 to SetClr (clear pins[0] and pins[2]): + await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x22) + await ctx.tick() + self.assertEqual(ctx.get(dut.pins[0].oe), 1) + self.assertEqual(ctx.get(dut.pins[1].oe), 0) + self.assertEqual(ctx.get(dut.pins[2].oe), 1) + self.assertEqual(ctx.get(dut.pins[3].oe), 0) + for n in range(4): + self.assertEqual(ctx.get(dut.pins[n].o), 0) + + # - write 0x0 to Output: + await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) + await ctx.tick() + for n in range(4): + self.assertEqual(ctx.get(dut.pins[n].oe), 1) + self.assertEqual(ctx.get(dut.pins[n].o), 0) + + # - write 0x44 to SetClr (set pins[1] and pins[3]): + await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x44) + await ctx.tick() + self.assertEqual(ctx.get(dut.pins[0].oe), 1) + self.assertEqual(ctx.get(dut.pins[1].oe), 0) + self.assertEqual(ctx.get(dut.pins[2].oe), 1) + self.assertEqual(ctx.get(dut.pins[3].oe), 0) + for n in range(4): + self.assertEqual(ctx.get(dut.pins[n].o), 0) + + # - write 0x0 to Output: + await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) + await ctx.tick() + for n in range(4): + self.assertEqual(ctx.get(dut.pins[n].oe), 1) + self.assertEqual(ctx.get(dut.pins[n].o), 0) + + # - write 0xff to SetClr (no-op): + await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0xff) + await ctx.tick() + for n in range(4): + self.assertEqual(ctx.get(dut.pins[n].oe), 1) + self.assertEqual(ctx.get(dut.pins[n].o), 0) + + # ALTERNATE mode ====================================================================== + + # - write Mode: + await self._csr_access(ctx, dut, mode_addr, w_stb=1, w_data=0b11111111) + await ctx.tick() + for n in range(4): + self.assertEqual(ctx.get(dut.alt_mode[n]), 1) + self.assertEqual(ctx.get(dut.pins[n].oe), 0) + self.assertEqual(ctx.get(dut.pins[n].o), 0) + + # - read Input: + ctx.set(dut.pins[1].i, 1) + ctx.set(dut.pins[3].i, 1) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) + ctx.set(dut.pins[1].i, 0) + ctx.set(dut.pins[3].i, 0) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0xa) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) + + # - write 0xf to Output: + await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0x0, w_stb=1, w_data=0xf) + await ctx.tick() + for n in range(4): + self.assertEqual(ctx.get(dut.pins[n].oe), 0) + self.assertEqual(ctx.get(dut.pins[n].o), 1) + + # - write 0x22 to SetClr (clear pins[0] and pins[2]): + await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x22) + await ctx.tick() + for n in range(4): + self.assertEqual(ctx.get(dut.pins[n].oe), 0) + self.assertEqual(ctx.get(dut.pins[0].o), 0) + self.assertEqual(ctx.get(dut.pins[1].o), 1) + self.assertEqual(ctx.get(dut.pins[2].o), 0) + self.assertEqual(ctx.get(dut.pins[3].o), 1) + + # - write 0x0 to Output: + await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) + await ctx.tick() + for n in range(4): + self.assertEqual(ctx.get(dut.pins[n].oe), 0) + self.assertEqual(ctx.get(dut.pins[n].o), 0) + + # - write 0x44 to SetClr (set pins[1] and pins[3]): + await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x44) + await ctx.tick() + for n in range(4): + self.assertEqual(ctx.get(dut.pins[n].oe), 0) + self.assertEqual(ctx.get(dut.pins[0].o), 0) + self.assertEqual(ctx.get(dut.pins[1].o), 1) + self.assertEqual(ctx.get(dut.pins[2].o), 0) + self.assertEqual(ctx.get(dut.pins[3].o), 1) + + # - write 0x0 to Output: + await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) + await ctx.tick() + for n in range(4): + self.assertEqual(ctx.get(dut.pins[n].oe), 0) + self.assertEqual(ctx.get(dut.pins[n].o), 0) + + # - write 0xff to SetClr (no-op): + await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0xff) + await ctx.tick() + for n in range(4): + self.assertEqual(ctx.get(dut.pins[n].oe), 0) + self.assertEqual(ctx.get(dut.pins[n].o), 0) + """ + sim = Simulator(dut) + sim.add_clock(1e-6) + sim.add_testbench(testbench) + with sim.write_vcd(vcd_file="test.vcd"): + sim.run() + + def test_sim_without_input_sync(self): + dut = GPIOPeripheral(pin_count=4, addr_width=2, data_width=8, input_stages=0) + input_addr = 0x1 + + async def testbench(ctx): + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) + ctx.set(dut.pins.gpio.i[1], 1) + ctx.set(dut.pins.gpio.i[3], 1) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0xa) + ctx.set(dut.pins.gpio.i[1], 0) + ctx.set(dut.pins.gpio.i[3], 0) + await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) + + sim = Simulator(dut) + sim.add_clock(1e-6) + sim.add_testbench(testbench) + with sim.write_vcd(vcd_file="test.vcd"): + sim.run() diff --git a/tests/test_hyperram.py b/tests/test_hyperram.py new file mode 100644 index 0000000..d5bf6d2 --- /dev/null +++ b/tests/test_hyperram.py @@ -0,0 +1,38 @@ +from amaranth import Module +from amaranth.sim import Simulator +from amaranth_orchard.memory import HyperRAM + + +def test_hyperram_smoke(): + m = Module() + m.submodules.hram = hram = HyperRAM() + sim = Simulator(m) + sim.add_clock(1e-6) + + def process(): + yield hram.data_bus.adr.eq(0x5A5A5A) + yield hram.data_bus.dat_w.eq(0xF0F0F0F0) + yield hram.data_bus.sel.eq(1) + yield hram.data_bus.we.eq(1) + yield hram.data_bus.stb.eq(1) + yield hram.data_bus.cyc.eq(1) + for i in range(100): + if (yield hram.data_bus.ack): + yield hram.data_bus.stb.eq(0) + yield hram.data_bus.cyc.eq(0) + yield + yield hram.data_bus.adr.eq(0x5A5A5A) + yield hram.data_bus.sel.eq(1) + yield hram.data_bus.we.eq(0) + yield hram.data_bus.stb.eq(1) + yield hram.data_bus.cyc.eq(1) + yield hram.pins.rwds.i.eq(1) + yield hram.pins.dq.i.eq(0xFF) + for i in range(100): + if (yield hram.data_bus.ack): + yield hram.data_bus.stb.eq(0) + yield hram.data_bus.cyc.eq(0) + yield + sim.add_sync_process(process) + with sim.write_vcd("hyperram.vcd", "hyperram.gtkw"): + sim.run()