diff --git a/.gitattributes b/.gitattributes index a5bf8cb..7a6857b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ docs/media/SP1-picture-with-pin-numbers.jpg filter=lfs diff=lfs merge=lfs -text +docs/media/pinouts.png filter=lfs diff=lfs merge=lfs -text diff --git a/PulseButton.vcd b/PulseButton.vcd new file mode 100644 index 0000000..42710e4 --- /dev/null +++ b/PulseButton.vcd @@ -0,0 +1,195 @@ +$comment Generated by Amaranth $end +$date 2025-09-20 22:27:02.816595 $end +$timescale 1 fs $end +$scope module bench $end +$scope module top $end +$var wire 1 ! clk $end +$var wire 1 " rst $end +$var wire 1 # i $end +$var wire 1 $ i$3 $end +$var wire 14 % counter $end +$var wire 1 & o $end +$var wire 1 ' o$6 $end +$var wire 1 ( last_seen $end +$scope module U$0 $end +$var wire 1 ! clk $end +$var wire 1 " rst $end +$var wire 1 # i $end +$var wire 1 ' o $end +$var wire 1 ) prevInValid $end +$var wire 14 * count $end +$var wire 1 + state $end +$var wire 1 , prevIn $end +$upscope $end +$upscope $end +$upscope $end +$enddefinitions $end +#0 +$dumpvars +0! +0" +0# +0$ +b0 % +0& +0' +0( +0) +b10011100010000 * +0+ +0, +$end +#500000000 +1! +1) +b0 * +#1000000000 +0! +#1500000000 +1! +1$ +1# +#2000000000 +0! +#2500000000 +1! +1+ +1, +b10011100010000 * +1' +#3000000000 +0! +#3500000000 +1! +1& +1( +#4000000000 +0! +#4500000000 +1! +0& +b10011100010000 % +#5000000000 +0! +#5500000000 +1! +b10011100001111 % +#6000000000 +0! +#6500000000 +1! +b10011100001110 % +0$ +0# +#7000000000 +0! +#7500000000 +1! +0, +b10011100001111 * +b10011100001101 % +#8000000000 +0! +#8500000000 +1! +b10011100001110 * +b10011100001100 % +#9000000000 +0! +#9500000000 +1! +b10011100001101 * +b10011100001011 % +#10000000000 +0! +#10500000000 +1! +b10011100001100 * +b10011100001010 % +#11000000000 +0! +#11500000000 +1! +b10011100001011 * +b10011100001001 % +1$ +1# +#12000000000 +0! +#12500000000 +1! +1, +b10011100010000 * +b10011100001000 % +#13000000000 +0! +#13500000000 +1! +b10011100000111 % +#14000000000 +0! +#14500000000 +1! +b10011100000110 % +#15000000000 +0! +#15500000000 +1! +b10011100000101 % +#16000000000 +0! +#16500000000 +1! +b10011100000100 % +0$ +0# +#17000000000 +0! +#17500000000 +1! +0, +b10011100001111 * +b10011100000011 % +#18000000000 +0! +#18500000000 +1! +b10011100001110 * +b10011100000010 % +#19000000000 +0! +#19500000000 +1! +b10011100001101 * +b10011100000001 % +#20000000000 +0! +#20500000000 +1! +b10011100001100 * +b10011100000000 % +#21000000000 +0! +#21500000000 +1! +b10011100001011 * +b10011011111111 % +#22000000000 +0! +#22500000000 +1! +b10011100001010 * +b10011011111110 % +#23000000000 +0! +#23500000000 +1! +b10011100001001 * +b10011011111101 % +#24000000000 +0! +#24500000000 +1! +b10011100001000 * +b10011011111100 % +#25000000000 diff --git a/ToggleButton.vcd b/ToggleButton.vcd new file mode 100644 index 0000000..da8a43c --- /dev/null +++ b/ToggleButton.vcd @@ -0,0 +1,171 @@ +$comment Generated by Amaranth $end +$date 2025-09-20 22:27:02.809849 $end +$timescale 1 fs $end +$scope module bench $end +$scope module top $end +$var wire 1 ! clk $end +$var wire 1 " rst $end +$var wire 1 # i $end +$var wire 1 $ i$3 $end +$var wire 1 % o $end +$var wire 1 & last_seen $end +$var wire 1 ' o$6 $end +$scope module U$0 $end +$var wire 1 ! clk $end +$var wire 1 " rst $end +$var wire 1 # i $end +$var wire 1 % o $end +$var wire 1 ( prevInValid $end +$var wire 14 ) count $end +$var wire 1 * state $end +$var wire 1 + prevIn $end +$upscope $end +$upscope $end +$upscope $end +$enddefinitions $end +#0 +$dumpvars +0! +0" +0# +0$ +0% +0& +0' +0( +b10011100010000 ) +0* +0+ +$end +#500000000 +1! +b0 ) +1( +#1000000000 +0! +#1500000000 +1! +1$ +1# +#2000000000 +0! +#2500000000 +1! +b10011100010000 ) +1* +1+ +1% +#3000000000 +0! +#3500000000 +1! +1& +1' +#4000000000 +0! +#4500000000 +1! +#5000000000 +0! +#5500000000 +1! +#6000000000 +0! +#6500000000 +1! +0$ +0# +#7000000000 +0! +#7500000000 +1! +b10011100001111 ) +0+ +#8000000000 +0! +#8500000000 +1! +b10011100001110 ) +#9000000000 +0! +#9500000000 +1! +b10011100001101 ) +#10000000000 +0! +#10500000000 +1! +b10011100001100 ) +#11000000000 +0! +#11500000000 +1! +b10011100001011 ) +1$ +1# +#12000000000 +0! +#12500000000 +1! +b10011100010000 ) +1+ +#13000000000 +0! +#13500000000 +1! +#14000000000 +0! +#14500000000 +1! +#15000000000 +0! +#15500000000 +1! +#16000000000 +0! +#16500000000 +1! +0$ +0# +#17000000000 +0! +#17500000000 +1! +b10011100001111 ) +0+ +#18000000000 +0! +#18500000000 +1! +b10011100001110 ) +#19000000000 +0! +#19500000000 +1! +b10011100001101 ) +#20000000000 +0! +#20500000000 +1! +b10011100001100 ) +#21000000000 +0! +#21500000000 +1! +b10011100001011 ) +#22000000000 +0! +#22500000000 +1! +b10011100001010 ) +#23000000000 +0! +#23500000000 +1! +b10011100001001 ) +#24000000000 +0! +#24500000000 +1! +b10011100001000 ) +#25000000000 diff --git a/docs/.obsidian/workspace.json b/docs/.obsidian/workspace.json index 9ce0ce1..e696794 100644 --- a/docs/.obsidian/workspace.json +++ b/docs/.obsidian/workspace.json @@ -11,17 +11,12 @@ "id": "be20a2e02f2b6437", "type": "leaf", "state": { - "type": "canvas", + "type": "image", "state": { - "file": "GameCube/Sp1 physical connector.canvas", - "viewState": { - "x": 72, - "y": -20, - "zoom": 0 - } + "file": "media/pinouts.png" }, - "icon": "lucide-layout-dashboard", - "title": "Sp1 physical connector" + "icon": "lucide-image", + "title": "pinouts" } } ] @@ -174,9 +169,13 @@ }, "active": "be20a2e02f2b6437", "lastOpenFiles": [ - "GameCube/SP1.md", - "GameCube/Sp1 physical connector.canvas", "media/SP1-picture-with-pin-numbers.jpg", + "IceBreaker FPGA/Pinouts for PMODs.md", + "media/pinouts.png", + "Pasted image 20250920193353.png", + "GameCube/Sp1 physical connector.canvas", + "IceBreaker FPGA", + "GameCube/SP1.md", "media/Unconfirmed 13874.crdownload", "media", "Environment/Amaranth-Hdl project setup.md", diff --git a/docs/IceBreaker FPGA/Pinouts for PMODs.md b/docs/IceBreaker FPGA/Pinouts for PMODs.md new file mode 100644 index 0000000..ae7cf91 --- /dev/null +++ b/docs/IceBreaker FPGA/Pinouts for PMODs.md @@ -0,0 +1,2 @@ + +![[pinouts.png]] \ No newline at end of file diff --git a/docs/media/pinouts.png b/docs/media/pinouts.png new file mode 100644 index 0000000..53a5e81 Binary files /dev/null and b/docs/media/pinouts.png differ diff --git a/rebbarb/.gitignore b/rebbarb/.gitignore index 378eac2..7d75298 100644 --- a/rebbarb/.gitignore +++ b/rebbarb/.gitignore @@ -1 +1,2 @@ build +__pycache__ diff --git a/rebbarb/__init__.py b/rebbarb/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rebbarb/debouncer.py b/rebbarb/debouncer.py new file mode 100644 index 0000000..83a6703 --- /dev/null +++ b/rebbarb/debouncer.py @@ -0,0 +1,38 @@ +from amaranth.build import Platform +from amaranth import * + +class Debouncer(Elaboratable): + + def __init__(self, cycles: int): + #signals + self.i = Signal() + self.o = Signal() + + #state + self.count = Signal(range(0, cycles+1), reset=cycles) + self.state = Signal() + self.prevInValid = Signal() + self.prevIn = Signal() + + def elaborate(self, platform: Platform): + m = Module() + + with m.If(self.prevInValid): + with m.If(self.count == 0): + with m.If(self.prevIn ^ self.i): + m.d.sync += self.count.eq(self.count.reset) + m.d.sync += self.state.eq(self.i) + with m.Elif(self.i == self.state): + m.d.sync += self.count.eq(self.count.reset) + m.d.sync += self.state.eq(self.i) + with m.Else(): + m.d.sync += self.count.eq(self.count - 1) + with m.Else(): + m.d.sync += self.count.eq(0) + m.d.sync += self.prevInValid.eq(Const(1)) + m.d.sync += self.state.eq(self.i) + + m.d.sync += self.prevIn.eq(self.i) + m.d.comb += self.o.eq(self.state) + + return m \ No newline at end of file diff --git a/rebbarb/pulse_button.py b/rebbarb/pulse_button.py new file mode 100644 index 0000000..0131570 --- /dev/null +++ b/rebbarb/pulse_button.py @@ -0,0 +1,59 @@ +from amaranth.build import Platform +from amaranth import * + +from debouncer import Debouncer + +class PulseButton(Elaboratable): + + def __init__(self): + #signals + self.i = Signal() + self.o = Signal() + + #state + self.last_seen = Signal() + self.counter = Signal(range(10000 + 1)) + + def elaborate(self, platform: Platform): + m = Module() + + debouncer = Debouncer(10000) + + m.submodules += debouncer + + m.d.comb += debouncer.i.eq(self.i) + + with m.If(self.counter != 0): + m.d.sync += self.counter.eq(self.counter - 1) + with m.Else(): + with m.If(self.o): + m.d.sync += self.o.eq(~self.o) + m.d.sync += self.counter.eq(10000) + + with m.If((debouncer.o != self.last_seen) & (debouncer.o == 1)): + m.d.sync += self.o.eq(~self.o) + + m.d.sync += self.last_seen.eq(debouncer.o) + + return m + + +from amaranth.sim import Simulator, Period + +dut = PulseButton() + +async def testbench(ctx): + await ctx.tick().repeat(2) + ctx.set(dut.i, True) + await ctx.tick().repeat(5) + ctx.set(dut.i, False) + await ctx.tick().repeat(5) + ctx.set(dut.i, True) + await ctx.tick().repeat(5) + ctx.set(dut.i, False) + +sim = Simulator(dut) +sim.add_clock(Period(MHz=1)) +sim.add_testbench(testbench) +with sim.write_vcd("PulseButton.vcd"): + sim.run_until(Period(MHz=1) * 25) \ No newline at end of file diff --git a/rebbarb/rebbarb.py b/rebbarb/rebbarb.py index 4f5468d..f8d139d 100644 --- a/rebbarb/rebbarb.py +++ b/rebbarb/rebbarb.py @@ -1,20 +1,21 @@ from amaranth import * -class LEDBlinker(Elaboratable): - def elaborate(self, platform): - m = Module() +from toggle_button import ToggleButton +from pulse_button import PulseButton +class ReBbaRb(Elaboratable): + def setup_pll(self, module, platform): cd_sync = ClockDomain("sync") - m.domains += cd_sync + module.domains += cd_sync platform.lookup(platform.default_clk).attrs['GLOBAL'] = False locked = Signal() pllout = Signal() - m.submodules.pll = Instance("SB_PLL40_PAD", + module.submodules.pll = Instance("SB_PLL40_PAD", p_FEEDBACK_PATH = "SIMPLE", p_DIVR = 0, - p_DIVF = 7, + p_DIVF = 5, p_DIVQ = 1, p_FILTER_RANGE = 1, @@ -25,6 +26,11 @@ class LEDBlinker(Elaboratable): o_PLLOUTCORE = cd_sync.clk ) + def elaborate(self, platform): + m = Module() + + self.setup_pll(m, platform) + ledr0 = platform.request("led_r") ledg0 = platform.request("led_g") @@ -33,14 +39,35 @@ class LEDBlinker(Elaboratable): ledg2 = platform.request("led_g", 2) ledg3 = platform.request("led_g", 3) ledg4 = platform.request("led_g", 4) + + btn1 = platform.request("button", 1) + btn2 = platform.request("button", 2) + btn3 = platform.request("button", 3) - half_freq = int(42e6 // 2) - timer = Signal(range(half_freq + 1)) + disabled = Signal() + + toggle1 = ToggleButton() + m.d.comb += toggle1.i.eq(btn2.i) + m.d.comb += disabled.eq(toggle1.o) + m.submodules += toggle1 + + pulse1 = PulseButton() + m.d.comb += pulse1.i.eq(btn1.i) + m.submodules += pulse1 + + pulse2 = PulseButton() + m.d.comb += pulse2.i.eq(btn3.i) + m.submodules += pulse2 + + init_half_freq = int(36e6 // 2) + half_freq = Signal(range((init_half_freq + 1) * 32), init=init_half_freq // 2) + timer = Signal(range((init_half_freq + 1) * 32)) init = Signal() with m.If(init == 0): m.d.sync += ledg0.o.eq(255) + m.d.sync += half_freq.eq(init_half_freq) m.d.sync += init.eq(1) with m.If(timer == half_freq): @@ -48,13 +75,151 @@ class LEDBlinker(Elaboratable): m.d.sync += ledg0.o.eq(~ledg0.o) m.d.sync += timer.eq(0) with m.Else(): - m.d.sync += timer.eq(timer + 1) + with m.If(~disabled): + m.d.sync += timer.eq(timer + 1) + + with m.If(pulse1.o): + m.d.sync += half_freq.eq(half_freq << 1) + m.d.sync += timer.eq(0) + + with m.If(pulse2.o): + m.d.sync += half_freq.eq(half_freq >> 1) + m.d.sync += timer.eq(0) return m -from amaranth_boards.icebreaker import ICEBreakerPlatform -from amaranth.vendor import SiliconBluePlatform; +import os +import subprocess +from amaranth.build import * +from amaranth.vendor import LatticeICE40Platform -platform = ICEBreakerPlatform() -platform.add_resources(platform.break_off_pmod) -platform.build(LEDBlinker(), do_program=True, verbose=True, nextpnr_opts='--freq 48') +def _SplitResources(*args, pins, invert=False, conn=None, attrs=None, default_name, dir): + assert isinstance(pins, (str, list, dict)) + + if isinstance(pins, str): + pins = pins.split() + if isinstance(pins, list): + pins = dict(enumerate(pins)) + + resources = [] + for number, pin in pins.items(): + ios = [Pins(pin, dir=dir, invert=invert, conn=conn)] + if attrs is not None: + ios.append(attrs) + resources.append(Resource.family(*args, number, default_name=default_name, ios=ios)) + return resources + +def UARTResource(*args, rx, tx, rts=None, cts=None, dtr=None, dsr=None, dcd=None, ri=None, + conn=None, attrs=None, role=None): + if any(line is not None for line in (rts, cts, dtr, dsr, dcd, ri)): + assert role in ("dce", "dte") + if role == "dte": + dce_to_dte = "i" + dte_to_dce = "o" + else: + dce_to_dte = "o" + dte_to_dce = "i" + + io = [] + io.append(Subsignal("rx", Pins(rx, dir="i", conn=conn, assert_width=1))) + io.append(Subsignal("tx", Pins(tx, dir="o", conn=conn, assert_width=1))) + if rts is not None: + io.append(Subsignal("rts", Pins(rts, dir=dte_to_dce, conn=conn, assert_width=1))) + if cts is not None: + io.append(Subsignal("cts", Pins(cts, dir=dce_to_dte, conn=conn, assert_width=1))) + if dtr is not None: + io.append(Subsignal("dtr", Pins(dtr, dir=dte_to_dce, conn=conn, assert_width=1))) + if dsr is not None: + io.append(Subsignal("dsr", Pins(dsr, dir=dce_to_dte, conn=conn, assert_width=1))) + if dcd is not None: + io.append(Subsignal("dcd", Pins(dcd, dir=dce_to_dte, conn=conn, assert_width=1))) + if ri is not None: + io.append(Subsignal("ri", Pins(ri, dir=dce_to_dte, conn=conn, assert_width=1))) + if attrs is not None: + io.append(attrs) + return Resource.family(*args, default_name="uart", ios=io) + +def SPIFlashResources(*args, cs_n, clk, copi, cipo, wp_n=None, hold_n=None, + conn=None, attrs=None): + resources = [] + + io_all = [] + if attrs is not None: + io_all.append(attrs) + io_all.append(Subsignal("cs", PinsN(cs_n, dir="o", conn=conn))) + io_all.append(Subsignal("clk", Pins(clk, dir="o", conn=conn, assert_width=1))) + + io_1x = list(io_all) + io_1x.append(Subsignal("copi", Pins(copi, dir="o", conn=conn, assert_width=1))) + io_1x.append(Subsignal("cipo", Pins(cipo, dir="i", conn=conn, assert_width=1))) + if wp_n is not None and hold_n is not None: + io_1x.append(Subsignal("wp", PinsN(wp_n, dir="o", conn=conn, assert_width=1))) + io_1x.append(Subsignal("hold", PinsN(hold_n, dir="o", conn=conn, assert_width=1))) + resources.append(Resource.family(*args, default_name="spi_flash", ios=io_1x, + name_suffix="1x")) + + io_2x = list(io_all) + io_2x.append(Subsignal("dq", Pins(" ".join([copi, cipo]), dir="io", conn=conn, + assert_width=2))) + resources.append(Resource.family(*args, default_name="spi_flash", ios=io_2x, + name_suffix="2x")) + + if wp_n is not None and hold_n is not None: + io_4x = list(io_all) + io_4x.append(Subsignal("dq", Pins(" ".join([copi, cipo, wp_n, hold_n]), dir="io", conn=conn, + assert_width=4))) + resources.append(Resource.family(*args, default_name="spi_flash", ios=io_4x, + name_suffix="4x")) + + return resources + +def LEDResources(*args, **kwargs): + return _SplitResources(*args, **kwargs, default_name="led", dir="o") + +def ButtonResources(*args, **kwargs): + return _SplitResources(*args, **kwargs, default_name="button", dir="i") + +class IceBreakerPlatform(LatticeICE40Platform): + device = "iCE40UP5K" + package = "SG48" + default_clk = "clk12" + resources = [ + Resource("clk12", 0, Pins("35", dir="i"), Clock(12e6), Attrs(GLOBAL=True, IO_STANDARD="SB_LVCMOS")), + + *LEDResources(pins="11 37", invert=True, attrs=Attrs(IO_STANDARD="SB_LVCMOS")), + # Semantic aliases + Resource("led_r", 0, PinsN("11", dir="o"), Attrs(IO_STANDARD="SB_LVCMOS")), + Resource("led_g", 0, PinsN("37", dir="o"), Attrs(IO_STANDARD="SB_LVCMOS")), + + *ButtonResources(pins="10", invert=True, attrs=Attrs(IO_STANDARD="SB_LVCMOS")), + + UARTResource(0, rx="6", tx="9", attrs=Attrs(IO_STANDARD="SB_LVTTL", PULLUP=1)), + + *SPIFlashResources(0, cs_n="16", clk="15", copi="14", cipo="17", wp_n="12", hold_n="13", attrs=Attrs(IO_STANDARD="SB_LVCMOS")), + + *LEDResources(pins={2: "7", 3: "1", 4: "2", 5: "8", 6: "3"}, conn=("pmod", 0), attrs=Attrs(IO_STANDARD="SB_LVCMOS")), + # Semantic aliases + Resource("led_r", 1, Pins("7", dir="o", conn=("pmod", 0)), Attrs(IO_STANDARD="SB_LVCMOS")), + Resource("led_g", 1, Pins("1", dir="o", conn=("pmod", 0)), Attrs(IO_STANDARD="SB_LVCMOS")), + Resource("led_g", 2, Pins("2", dir="o", conn=("pmod", 0)), Attrs(IO_STANDARD="SB_LVCMOS")), + Resource("led_g", 3, Pins("8", dir="o", conn=("pmod", 0)), Attrs(IO_STANDARD="SB_LVCMOS")), + Resource("led_g", 4, Pins("3", dir="o", conn=("pmod", 0)), Attrs(IO_STANDARD="SB_LVCMOS")), + *ButtonResources(pins={1: "9", 2: "4", 3: "10"}, conn=("pmod", 0), attrs=Attrs(IO_STANDARD="SB_LVCMOS")), + ] + + connectors = [ + Connector("pmod", 0, " 4 2 47 45 - - 3 48 46 44 - -"), # PMOD1A + Connector("pmod", 1, "43 38 34 31 - - 42 36 32 28 - -"), # PMOD1B + Connector("pmod", 2, "27 25 21 19 - - 26 23 20 18 - -"), # PMOD2 + ] + + def toolchain_program(self, products, name): + iceprog = os.environ.get("ICEPROG", "iceprog") + with products.extract("{}.bin".format(name)) as bitstream_filename: + subprocess.check_call([iceprog, bitstream_filename]) + +import os +import subprocess + +platform = IceBreakerPlatform() +platform.build(ReBbaRb(), do_program=True, verbose=True) \ No newline at end of file diff --git a/rebbarb/toggle_button.py b/rebbarb/toggle_button.py new file mode 100644 index 0000000..f91547b --- /dev/null +++ b/rebbarb/toggle_button.py @@ -0,0 +1,51 @@ +from amaranth.build import Platform +from amaranth import * + +from debouncer import Debouncer + +class ToggleButton(Elaboratable): + + def __init__(self): + #signals + self.i = Signal() + self.o = Signal() + + #state + self.last_seen = Signal() + + def elaborate(self, platform: Platform): + m = Module() + + debouncer = Debouncer(10000) + + m.submodules += debouncer + + m.d.comb += debouncer.i.eq(self.i) + + with m.If((debouncer.o != self.last_seen) & (debouncer.o == 1)): + m.d.sync += self.o.eq(~self.o) + + m.d.sync += self.last_seen.eq(debouncer.o) + + return m + + +from amaranth.sim import Simulator, Period + +dut = ToggleButton() + +async def testbench(ctx): + await ctx.tick().repeat(2) + ctx.set(dut.i, True) + await ctx.tick().repeat(5) + ctx.set(dut.i, False) + await ctx.tick().repeat(5) + ctx.set(dut.i, True) + await ctx.tick().repeat(5) + ctx.set(dut.i, False) + +sim = Simulator(dut) +sim.add_clock(Period(MHz=1)) +sim.add_testbench(testbench) +with sim.write_vcd("ToggleButton.vcd"): + sim.run_until(Period(MHz=1) * 25) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 8c20c0a..2318e38 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -amaranth==0.5.7 +amaranth @ git+https://github.com/amaranth-lang/amaranth@main amaranth-boards @ git+https://github.com/amaranth-lang/amaranth-boards.git@7e24efe2f6e95afddd0c1b56f1a9423c48caa472 amaranth-yosys==0.50.0.0.post115 importlib_resources==6.5.2