Skip to content

Commit 7388ec2

Browse files
robtaylorclaude
andcommitted
Replace tutorial placeholder with comprehensive guide
- Replaced the existing tutorial placeholder with a complete tutorial - Created code examples for each section of the tutorial - Tutorial covers basic concepts to advanced topics like FSMs - Includes troubleshooting and glossary sections - Maintained references to existing community tutorials 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 76da9d8 commit 7388ec2

File tree

8 files changed

+754
-8
lines changed

8 files changed

+754
-8
lines changed

docs/_code/and_gate.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from amaranth import *
2+
from amaranth.back import verilog
3+
4+
class AndGate(Elaboratable):
5+
def __init__(self):
6+
# Input ports
7+
self.a = Signal()
8+
self.b = Signal()
9+
# Output port
10+
self.y = Signal()
11+
12+
def elaborate(self, platform):
13+
# The 'elaborate' method builds the actual circuit
14+
m = Module()
15+
# y = a AND b
16+
m.d.comb += self.y.eq(self.a & self.b)
17+
return m
18+
19+
# Create the gate
20+
gate = AndGate()
21+
22+
# Generate Verilog (for viewing or using with other tools)
23+
with open("and_gate.v", "w") as f:
24+
f.write(verilog.convert(gate, ports=[gate.a, gate.b, gate.y]))
25+
26+
# How to run: pdm run python and_gate.py
27+
# This will generate and_gate.v

docs/_code/blinky.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from amaranth import *
2+
3+
class Blinky(Elaboratable):
4+
def __init__(self):
5+
# No parameters needed for this simple example
6+
pass
7+
8+
def elaborate(self, platform):
9+
# The 'elaborate' method transforms our Python description into a hardware circuit
10+
11+
# Get the LED from the platform (if running on actual hardware)
12+
if platform is not None:
13+
led = platform.request("led", 0)
14+
else:
15+
# For simulation, create a dummy LED signal
16+
led = Signal()
17+
18+
# Create a timer (24-bit counter)
19+
# This will count from 0 to 2^24-1 and then wrap around
20+
timer = Signal(24)
21+
22+
m = Module()
23+
24+
# Increment timer every clock cycle
25+
# 'd.sync' means this happens on the rising edge of the clock
26+
m.d.sync += timer.eq(timer + 1)
27+
28+
# Connect LED to the most significant bit of the timer
29+
# timer[-1] means "the last bit" (most significant bit)
30+
# This makes the LED toggle on/off when the timer overflows
31+
m.d.comb += led.o.eq(timer[-1])
32+
33+
return m
34+
35+
# This lets us run this file directly or include it in other scripts
36+
if __name__ == "__main__":
37+
from amaranth.sim import Simulator, Period
38+
39+
# Create our circuit
40+
dut = Blinky()
41+
42+
# Set up a simple simulation to watch the LED blink
43+
sim = Simulator(dut)
44+
sim.add_clock(Period(MHz=1)) # 1 MHz clock (1μs period)
45+
46+
# Run simulation and generate a waveform file
47+
with sim.write_vcd("blinky.vcd"):
48+
sim.run_until(100 * 1_000_000) # Run for 100ms of simulated time
49+
50+
# How to run: pdm run python blinky.py
51+
# This will generate blinky.vcd, which you can view with GTKWave

docs/_code/controlled_blinker.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from amaranth import *
2+
from amaranth.lib import wiring
3+
from amaranth.lib.wiring import In, Out
4+
5+
# Import our UpCounter
6+
from up_counter import UpCounter
7+
8+
class ControlledBlinker(Elaboratable):
9+
"""
10+
An LED blinker that uses a counter to control blink rate.
11+
"""
12+
def __init__(self, freq_hz=1):
13+
"""
14+
Create a blinker with specified frequency.
15+
16+
Args:
17+
freq_hz: Blink frequency in Hz (defaults to 1Hz)
18+
"""
19+
self.freq_hz = freq_hz
20+
21+
def elaborate(self, platform):
22+
m = Module()
23+
24+
# Get system clock frequency (for actual hardware)
25+
if platform is not None:
26+
sys_clock_freq = platform.default_clk_frequency
27+
else:
28+
# For simulation, assume 1MHz clock
29+
sys_clock_freq = 1_000_000
30+
31+
# Calculate counter limit based on desired blink frequency
32+
# The counter will overflow twice per cycle (on-off)
33+
counter_limit = int(sys_clock_freq / (2 * self.freq_hz)) - 1
34+
35+
# Create our counter submodule
36+
counter = UpCounter(counter_limit)
37+
# Add it to our module with a name
38+
m.submodules.counter = counter
39+
40+
# Create a toggle flip-flop for LED state
41+
led_state = Signal(1)
42+
43+
# Always enable the counter
44+
m.d.comb += counter.en.eq(1)
45+
46+
# Toggle LED state on overflow
47+
with m.If(counter.ovf):
48+
m.d.sync += led_state.eq(~led_state)
49+
50+
# Connect to the LED if running on hardware
51+
if platform is not None:
52+
led = platform.request("led", 0)
53+
m.d.comb += led.o.eq(led_state)
54+
55+
return m
56+
57+
# Example usage
58+
if __name__ == "__main__":
59+
from amaranth.sim import Simulator, Period
60+
61+
# Create a 2Hz blinker
62+
dut = ControlledBlinker(freq_hz=2)
63+
64+
# Basic simulation to observe blinking
65+
sim = Simulator(dut)
66+
sim.add_clock(Period(MHz=1)) # 1MHz system clock
67+
68+
# Add a simple test to just run for a while
69+
def test_bench():
70+
pass
71+
72+
sim.add_process(test_bench)
73+
74+
# Run for 2 seconds (enough to see multiple blinks at 2Hz)
75+
with sim.write_vcd("blinker_system.vcd", "blinker_system.gtkw"):
76+
sim.run_until(2_000_000) # 2M ns = 2 seconds
77+
78+
print("Simulation complete. View waveform with 'gtkwave blinker_system.vcd'")
79+
80+
# Generate Verilog
81+
from amaranth.back import verilog
82+
83+
with open("blinker_system.v", "w") as f:
84+
f.write(verilog.convert(dut))
85+
86+
print("Generated blinker_system.v")

docs/_code/program_icestick.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from amaranth import *
2+
# This import assumes you have amaranth-boards package installed
3+
# If using a different board, import the appropriate platform
4+
try:
5+
from amaranth_boards.icestick import ICEStickPlatform
6+
7+
# Import our blinker
8+
from controlled_blinker import ControlledBlinker
9+
10+
# Create a platform for the iCEStick board
11+
platform = ICEStickPlatform()
12+
13+
# Create a 1Hz blinker (adjust frequency as needed)
14+
blinker = ControlledBlinker(freq_hz=1)
15+
16+
# Build and program
17+
platform.build(blinker, do_program=True)
18+
except ImportError:
19+
print("This example requires amaranth-boards package")
20+
print("Install it with: pdm add amaranth-boards")
21+
22+
# How to run: pdm run python program_icestick.py

docs/_code/uart_receiver.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
from amaranth import *
2+
from amaranth.lib import wiring
3+
from amaranth.lib.wiring import In, Out
4+
5+
class UARTReceiver(wiring.Component):
6+
"""
7+
A UART (serial) receiver that converts serial data to parallel.
8+
9+
UART uses start and stop bits to frame each byte:
10+
- Line is high when idle
11+
- Start bit is low (0)
12+
- 8 data bits follow
13+
- Stop bit is high (1)
14+
15+
Parameters
16+
----------
17+
divisor : int
18+
Clock divisor for baud rate (system_clock / baud_rate)
19+
Example: 100MHz system clock, 9600 baud → divisor = 10,417
20+
21+
Attributes
22+
----------
23+
i : Signal, in
24+
Serial input line
25+
ack : Signal, in
26+
Acknowledgment (read the received byte)
27+
data : Signal, out
28+
8-bit received data
29+
rdy : Signal, out
30+
Data ready flag (high when byte received)
31+
err : Signal, out
32+
Error flag (high on framing error)
33+
"""
34+
35+
# Interface
36+
i: In(1) # Input bit (serial line)
37+
data: Out(8) # Received byte (parallel output)
38+
rdy: Out(1) # Data ready flag
39+
ack: In(1) # Acknowledgment
40+
err: Out(1) # Error flag
41+
42+
def __init__(self, divisor):
43+
super().__init__()
44+
self.divisor = divisor # Clock divisor for baud rate
45+
46+
def elaborate(self, platform):
47+
m = Module()
48+
49+
# Baud rate generator
50+
# This creates a "strobe" (stb) that pulses once per bit period
51+
ctr = Signal(range(self.divisor)) # Counter for clock division
52+
stb = Signal() # Strobe signal (pulses when we should sample)
53+
54+
# When counter reaches zero, reset it and pulse the strobe
55+
with m.If(ctr == 0):
56+
m.d.sync += ctr.eq(self.divisor - 1) # Reset counter
57+
m.d.comb += stb.eq(1) # Pulse strobe
58+
with m.Else():
59+
m.d.sync += ctr.eq(ctr - 1) # Decrement counter
60+
61+
# Bit counter (counts 8 data bits)
62+
bit = Signal(3) # 3 bits to count 0-7
63+
64+
# FSM (Finite State Machine) for UART reception
65+
with m.FSM() as fsm:
66+
# Initial state: wait for start bit
67+
with m.State("START"):
68+
with m.If(~self.i): # If input goes low (start bit detected)
69+
m.next = "DATA" # Move to DATA state
70+
m.d.sync += [
71+
# Sample in middle of bit by setting counter to half divisor
72+
ctr.eq(self.divisor // 2),
73+
# Prepare to receive 8 bits (bit 7 down to bit 0)
74+
bit.eq(7),
75+
]
76+
77+
# Receiving data bits
78+
with m.State("DATA"):
79+
with m.If(stb): # On each baud strobe (sampling point)
80+
m.d.sync += [
81+
bit.eq(bit - 1), # Decrement bit counter
82+
# Cat() concatenates bits - this shifts received bit into the data
83+
self.data.eq(Cat(self.i, self.data[:-1])),
84+
]
85+
with m.If(bit == 0): # If all bits received
86+
m.next = "STOP" # Move to STOP state
87+
88+
# Check stop bit
89+
with m.State("STOP"):
90+
with m.If(stb): # On baud strobe
91+
with m.If(self.i): # If input is high (valid stop bit)
92+
m.next = "DONE" # Move to DONE state
93+
with m.Else(): # If input is low (invalid stop bit)
94+
m.next = "ERROR" # Move to ERROR state
95+
96+
# Data ready - wait for acknowledgment
97+
with m.State("DONE"):
98+
m.d.comb += self.rdy.eq(1) # Set ready flag
99+
with m.If(self.ack): # When acknowledged
100+
m.next = "START" # Go back to START for next byte
101+
102+
# Error state - stay here until reset
103+
# fsm.ongoing() checks if FSM is in a specific state
104+
m.d.comb += self.err.eq(fsm.ongoing("ERROR"))
105+
with m.State("ERROR"):
106+
pass # Do nothing (stay in error state)
107+
108+
return m
109+
110+
# Example usage
111+
if __name__ == "__main__":
112+
from amaranth.back import verilog
113+
114+
# Create a UART receiver for 9600 baud with a 1MHz clock
115+
uart = UARTReceiver(divisor=104) # 1,000,000 / 9600 ≈ 104
116+
117+
# Generate Verilog
118+
with open("uart_rx.v", "w") as f:
119+
f.write(verilog.convert(uart))
120+
121+
print("Generated uart_rx.v")

0 commit comments

Comments
 (0)