Added Debouncer ToggleButton and PulseButton

This commit is contained in:
Dennis Brentjes 2025-09-20 22:44:04 +02:00
parent eea189ae61
commit ac755932d5
13 changed files with 708 additions and 26 deletions

1
.gitattributes vendored
View File

@ -1 +1,2 @@
docs/media/SP1-picture-with-pin-numbers.jpg filter=lfs diff=lfs merge=lfs -text 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

195
PulseButton.vcd Normal file
View File

@ -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

171
ToggleButton.vcd Normal file
View File

@ -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

View File

@ -11,17 +11,12 @@
"id": "be20a2e02f2b6437", "id": "be20a2e02f2b6437",
"type": "leaf", "type": "leaf",
"state": { "state": {
"type": "canvas", "type": "image",
"state": { "state": {
"file": "GameCube/Sp1 physical connector.canvas", "file": "media/pinouts.png"
"viewState": {
"x": 72,
"y": -20,
"zoom": 0
}
}, },
"icon": "lucide-layout-dashboard", "icon": "lucide-image",
"title": "Sp1 physical connector" "title": "pinouts"
} }
} }
] ]
@ -174,9 +169,13 @@
}, },
"active": "be20a2e02f2b6437", "active": "be20a2e02f2b6437",
"lastOpenFiles": [ "lastOpenFiles": [
"GameCube/SP1.md",
"GameCube/Sp1 physical connector.canvas",
"media/SP1-picture-with-pin-numbers.jpg", "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/Unconfirmed 13874.crdownload",
"media", "media",
"Environment/Amaranth-Hdl project setup.md", "Environment/Amaranth-Hdl project setup.md",

View File

@ -0,0 +1,2 @@
![[pinouts.png]]

BIN
docs/media/pinouts.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

1
rebbarb/.gitignore vendored
View File

@ -1 +1,2 @@
build build
__pycache__

0
rebbarb/__init__.py Normal file
View File

38
rebbarb/debouncer.py Normal file
View File

@ -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

59
rebbarb/pulse_button.py Normal file
View File

@ -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)

View File

@ -1,20 +1,21 @@
from amaranth import * from amaranth import *
class LEDBlinker(Elaboratable): from toggle_button import ToggleButton
def elaborate(self, platform): from pulse_button import PulseButton
m = Module()
class ReBbaRb(Elaboratable):
def setup_pll(self, module, platform):
cd_sync = ClockDomain("sync") cd_sync = ClockDomain("sync")
m.domains += cd_sync module.domains += cd_sync
platform.lookup(platform.default_clk).attrs['GLOBAL'] = False platform.lookup(platform.default_clk).attrs['GLOBAL'] = False
locked = Signal() locked = Signal()
pllout = Signal() pllout = Signal()
m.submodules.pll = Instance("SB_PLL40_PAD", module.submodules.pll = Instance("SB_PLL40_PAD",
p_FEEDBACK_PATH = "SIMPLE", p_FEEDBACK_PATH = "SIMPLE",
p_DIVR = 0, p_DIVR = 0,
p_DIVF = 7, p_DIVF = 5,
p_DIVQ = 1, p_DIVQ = 1,
p_FILTER_RANGE = 1, p_FILTER_RANGE = 1,
@ -25,6 +26,11 @@ class LEDBlinker(Elaboratable):
o_PLLOUTCORE = cd_sync.clk o_PLLOUTCORE = cd_sync.clk
) )
def elaborate(self, platform):
m = Module()
self.setup_pll(m, platform)
ledr0 = platform.request("led_r") ledr0 = platform.request("led_r")
ledg0 = platform.request("led_g") ledg0 = platform.request("led_g")
@ -34,13 +40,34 @@ class LEDBlinker(Elaboratable):
ledg3 = platform.request("led_g", 3) ledg3 = platform.request("led_g", 3)
ledg4 = platform.request("led_g", 4) ledg4 = platform.request("led_g", 4)
half_freq = int(42e6 // 2) btn1 = platform.request("button", 1)
timer = Signal(range(half_freq + 1)) btn2 = platform.request("button", 2)
btn3 = platform.request("button", 3)
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() init = Signal()
with m.If(init == 0): with m.If(init == 0):
m.d.sync += ledg0.o.eq(255) m.d.sync += ledg0.o.eq(255)
m.d.sync += half_freq.eq(init_half_freq)
m.d.sync += init.eq(1) m.d.sync += init.eq(1)
with m.If(timer == half_freq): with m.If(timer == half_freq):
@ -48,13 +75,151 @@ class LEDBlinker(Elaboratable):
m.d.sync += ledg0.o.eq(~ledg0.o) m.d.sync += ledg0.o.eq(~ledg0.o)
m.d.sync += timer.eq(0) m.d.sync += timer.eq(0)
with m.Else(): with m.Else():
with m.If(~disabled):
m.d.sync += timer.eq(timer + 1) 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 return m
from amaranth_boards.icebreaker import ICEBreakerPlatform import os
from amaranth.vendor import SiliconBluePlatform; import subprocess
from amaranth.build import *
from amaranth.vendor import LatticeICE40Platform
platform = ICEBreakerPlatform() def _SplitResources(*args, pins, invert=False, conn=None, attrs=None, default_name, dir):
platform.add_resources(platform.break_off_pmod) assert isinstance(pins, (str, list, dict))
platform.build(LEDBlinker(), do_program=True, verbose=True, nextpnr_opts='--freq 48')
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)

51
rebbarb/toggle_button.py Normal file
View File

@ -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)

View File

@ -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-boards @ git+https://github.com/amaranth-lang/amaranth-boards.git@7e24efe2f6e95afddd0c1b56f1a9423c48caa472
amaranth-yosys==0.50.0.0.post115 amaranth-yosys==0.50.0.0.post115
importlib_resources==6.5.2 importlib_resources==6.5.2