# type: ignore """amaranth-soc GPIO MCVE.""" # /// script # requires-python = ">=3.11" # dependencies = [ # "sentinel@git+https://github.com/cr1901/sentinel@next", # "amaranth>=0.5.4", # "amaranth-soc @ git+https://github.com/amaranth-lang/amaranth-soc", # "amaranth-boards @ git+https://github.com/amaranth-lang/amaranth-boards", # "bronzebeard>=0.2.1", # "tabulate>=0.9.0", # ] # /// import argparse from random import randint from pathlib import Path from bronzebeard.asm import assemble from amaranth import Module from amaranth_soc.wishbone.sram import WishboneSRAM from amaranth_soc import wishbone, csr, gpio from amaranth_soc.csr.wishbone import WishboneCSRBridge from amaranth.lib.wiring import Elaboratable, connect, flipped from amaranth.build import ResourceError, Resource, Pins from amaranth_boards import icebreaker from tabulate import tabulate from sentinel.top import Top # Reimplementation of nextpnr-ice40's AttoSoC example (https://github.com/YosysHQ/nextpnr/tree/master/ice40/smoketest/attosoc), # noqa: E501 # to exercise attaching Sentinel to amaranth-soc's wishbone components. class AttoSoC(Elaboratable): """AttoSoC constructor. Create a Sentinel SoC with LEDs/GPIO, timer, and UART. Parameters ---------- num_bytes: int Size of the RAM in bytes. bus_type: BusType Bus used for peripherals. Attributes ---------- cpu The Sentinel CPU mem CPU Memory leds The LEDs/GPIO peripheral timer The timer peripheral serial The UART peripheral """ # CSR is the default because it's what's encouraged. However, the default # for the demo is WB because that's what fits on the ICE40HX1K! def __init__(self, *, num_bytes=0x400, pin_count=8): self.cpu = Top() self.mem = WishboneSRAM(size=num_bytes, data_width=32, granularity=8) self.decoder = wishbone.Decoder(addr_width=30, data_width=32, granularity=8, alignment=25) self.leds = gpio.Peripheral(pin_count=pin_count, addr_width=4, data_width=8) @property def rom(self): """Memory contents of user program, if any.""" return self.mem.init @rom.setter def rom(self, source_or_list): if isinstance(source_or_list, str): insns = assemble(source_or_list) self.mem.init = [int.from_bytes(insns[adr:adr + 4], byteorder="little") for adr in range(0, len(insns), 4)] elif isinstance(source_or_list, (bytes, bytearray)): self.mem.init = [int.from_bytes(source_or_list[adr:adr + 4], byteorder="little") for adr in range(0, len(source_or_list), 4)] else: self.mem.init = source_or_list def elaborate(self, plat): # noqa: D102 m = Module() m.submodules.cpu = self.cpu m.submodules.mem = self.mem m.submodules.leds = self.leds m.submodules.decoder = self.decoder if plat: for i in range(8): try: led = plat.request("led", i) except ResourceError: break m.d.comb += led.o.eq(self.leds.pins[i].o) # for i in range(8): # try: # gpio = plat.request("gpio", i) # except ResourceError: # break # m.d.comb += [ # self.leds.pins[i + 8].i.eq(gpio.i), # gpio.oe.eq(self.leds.pins[i + 8].oe), # gpio.o.eq(self.leds.pins[i + 8].o) # ] self.decoder.add(flipped(self.mem.wb_bus)) # CSR (has to be done first other mem map "frozen" errors?) periph_decode = csr.Decoder(addr_width=25, data_width=8, alignment=23) periph_decode.add(self.leds.bus, name="leds", addr=0) # Connect peripherals to Wishbone periph_wb = WishboneCSRBridge(periph_decode.bus, data_width=32) self.decoder.add(flipped(periph_wb.wb_bus)) m.submodules.periph_bus = periph_decode m.submodules.periph_wb = periph_wb def destruct_res(res): ls = [] for c in res.path: if isinstance(c, str): ls.append(c) elif isinstance(c, (tuple, list)): ls.extend(c) else: raise ValueError("can only create a name for a str, " f"tuple, or list, not {type(c)}") return ("/".join(ls), res.start, res.end, res.width) print(tabulate(map(destruct_res, self.decoder.bus.memory_map.all_resources()), intfmt=("", "#010x", "#010x", ""), headers=["name", "start", "end", "width"])) connect(m, self.cpu.bus, self.decoder.bus) return m def demo(args): """AttoSoC generator entry point.""" if args.r: rom = [randint(0, 0xffffffff) for _ in range(0x400)] else: io_out = "sb s0,3(s1)" if args.c == 8 else "sw s0,6(s1)" # Primes test firmware from tests and nextpnr AttoSoC. rom = f""" li s0,2 lui s1,0x2000 # IO port at 0x2000000 li s3,256 outer: addi s0,s0,1 blt s0,s3,noinit li s0,2 noinit: li s2,2 next_int: bge s2,s0,write_io mv a0,s0 mv a1,s2 call prime? beqz a0,not_prime addi s2,s2,1 j next_int write_io: {io_out} call delay not_prime: j outer prime?: li t0,1 submore: sub a0,a0,a1 bge a0,t0,submore ret delay: li t0,360000 countdown: addi t0,t0,-1 bnez t0,countdown ret """ asoc = AttoSoC(num_bytes=0x1000, pin_count=args.c) asoc.rom = rom plat = icebreaker.ICEBreakerPlatform() plat.default_rst = "button" # Cheating a bit :). plat.add_resources([*plat.break_off_pmod, Resource("gpio", 0, Pins("1", dir="io", conn=("pmod", 0))), Resource("gpio", 1, Pins("2", dir="io", conn=("pmod", 0))), Resource("gpio", 2, Pins("3", dir="io", conn=("pmod", 0))), Resource("gpio", 3, Pins("4", dir="io", conn=("pmod", 0))), Resource("gpio", 4, Pins("7", dir="io", conn=("pmod", 0))), Resource("gpio", 5, Pins("8", dir="io", conn=("pmod", 0))), Resource("gpio", 6, Pins("9", dir="io", conn=("pmod", 0))), Resource("gpio", 7, Pins("10", dir="io", conn=("pmod", 0))) ]) name = "rand" if args.r else "top" plan = plat.build(asoc, name=name, do_build=False, debug_verilog=True) if not args.b: args.b = "build" if not args.n: plan.execute_local(Path(args.b)) else: plan.extract(Path(args.b)) local_path = Path(args.b) if args.x: with open(Path(local_path) / Path(args.x).with_suffix(".hex"), "w") as fp: # noqa: E501 fp.writelines(f"{i:08x}\n" for i in asoc.rom) def main(): """AttoSoC generator command-line parser.""" parser = argparse.ArgumentParser(description="Sentinel GPIO Demo generator") # noqa: E501 parser.add_argument("-c", help="GPIO pin count", choices=(8, 16), default=8, type=int) parser.add_argument("-n", help="dry run", action="store_true") parser.add_argument("-b", help="build directory (default build)") parser.add_argument("-p", help="build platform", choices=("icebreaker",), default="icebreaker") parser.add_argument("-r", help="use random numbers to fill firmware " "(use with -x and -n)", action="store_true") parser.add_argument("-x", help="generate a hex file of firmware in build " "dir (use with {ice,ecp}bram)", metavar="BASENAME", default=None) args = parser.parse_args() demo(args) if __name__ == "__main__": main()