diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index 3a002e1b7..59a80db26 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -23,6 +23,8 @@ jobs:
include:
- python-version: '3.14-dev'
allow-failure: true
+ env:
+ PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1
continue-on-error: ${{ matrix.allow-failure }}
name: 'test (${{ matrix.python-version }})'
steps:
@@ -41,6 +43,7 @@ jobs:
sudo apt-get update
sudo apt-get install yices2
pip install codecov build
+ export PYO3_USE_ABI3_FORWARD_COMPATIBILITY=${{ matrix.env.PYO3_USE_ABI3_FORWARD_COMPATIBILITY }}
pdm install --dev
- name: Cache YoWASP build products
uses: actions/cache@v4
diff --git a/.github/workflows/tutorial-comprehension-test.yml b/.github/workflows/tutorial-comprehension-test.yml
new file mode 100644
index 000000000..3883fc3c3
--- /dev/null
+++ b/.github/workflows/tutorial-comprehension-test.yml
@@ -0,0 +1,132 @@
+name: Tutorial Comprehension Test
+
+on:
+ push:
+ branches: [ main ]
+ paths:
+ - 'docs/tutorial.rst'
+ - '.github/workflows/tutorial-comprehension-test.yml'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - 'docs/tutorial.rst'
+ - '.github/workflows/tutorial-comprehension-test.yml'
+ workflow_dispatch: # Allow manual trigger
+
+jobs:
+ analyze-tutorial:
+ name: Analyze Tutorial with Claude
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '18'
+
+ - name: Install Anthropic SDK
+ run: npm install @anthropic-ai/sdk
+
+ - name: Create tutorial analysis script
+ run: |
+ cat > analyze_tutorial.js << 'EOF'
+ const fs = require('fs');
+ const Anthropic = require('@anthropic-ai/sdk');
+
+ // Initialize Anthropic client
+ const anthropic = new Anthropic({
+ apiKey: process.env.ANTHROPIC_API_KEY,
+ });
+
+ async function analyzeTutorial() {
+ // Read the tutorial content
+ const tutorialContent = fs.readFileSync('docs/tutorial.rst', 'utf8');
+
+ // Create the prompt for Claude
+ const prompt = `
+ ${tutorialContent}
+
+
+ You are an expert in hardware design, HDLs, and educational content. Please analyze the above Amaranth HDL tutorial and perform the following tasks:
+
+ 1. Consistency check:
+ - Are all code examples syntactically correct?
+ - Do the examples align with the explanations?
+ - Are there any missing dependencies or imports?
+ - Would a beginner be able to run these examples without errors?
+
+ 2. Comprehensibility assessment:
+ - How well does the tutorial explain hardware concepts to beginners?
+ - Are there any concepts that need better explanation?
+ - Is the progression of examples logical?
+ - Are there any gaps in the learning journey?
+
+ 3. Identify any potential improvements:
+ - What could make this tutorial more effective?
+ - Are there missing explanations for important concepts?
+ - What additional examples might be helpful?
+
+ Provide your assessment in a structured format with clear headings and bullet points.`;
+
+ try {
+ console.log("Sending request to Claude...");
+
+ // Call Claude with the prompt
+ const response = await anthropic.messages.create({
+ model: "claude-3-opus-20240229",
+ max_tokens: 4000,
+ messages: [
+ { role: "user", content: prompt }
+ ],
+ temperature: 0.2,
+ });
+
+ // Write Claude's analysis to a file
+ fs.writeFileSync('tutorial_analysis.md', response.content[0].text);
+ console.log("Analysis complete. Results written to tutorial_analysis.md");
+
+ // Also print a summary to the console
+ console.log("\n=== SUMMARY OF ANALYSIS ===\n");
+ console.log(response.content[0].text.substring(0, 1000) + "...");
+
+ } catch (error) {
+ console.error("Error calling Claude API:", error);
+ process.exit(1);
+ }
+ }
+
+ analyzeTutorial();
+ EOF
+
+ chmod +x analyze_tutorial.js
+
+ - name: Check ANTHROPIC_API_KEY is set
+ id: check_api_key
+ run: |
+ if [ -n "${{ secrets.ANTHROPIC_API_KEY }}" ]; then
+ echo "API key is set, proceeding with Claude analysis"
+ echo "has_api_key=true" >> $GITHUB_OUTPUT
+ else
+ echo "ANTHROPIC_API_KEY is not set. Skipping Claude analysis."
+ echo "has_api_key=false" >> $GITHUB_OUTPUT
+ echo "## ⚠️ Warning - Claude Analysis Skipped" >> $GITHUB_STEP_SUMMARY
+ echo "* ANTHROPIC_API_KEY secret is not configured in this repository" >> $GITHUB_STEP_SUMMARY
+ echo "* Analysis will be skipped for now" >> $GITHUB_STEP_SUMMARY
+ echo "* This test will run automatically once the secret is configured" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ - name: Analyze tutorial with Claude
+ if: steps.check_api_key.outputs.has_api_key == 'true'
+ env:
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ run: node analyze_tutorial.js
+
+ - name: Archive analysis results
+ if: steps.check_api_key.outputs.has_api_key == 'true'
+ uses: actions/upload-artifact@v4
+ with:
+ name: tutorial-analysis
+ path: tutorial_analysis.md
\ No newline at end of file
diff --git a/.github/workflows/tutorial-execution-test.yml b/.github/workflows/tutorial-execution-test.yml
new file mode 100644
index 000000000..6984b853c
--- /dev/null
+++ b/.github/workflows/tutorial-execution-test.yml
@@ -0,0 +1,269 @@
+name: Tutorial Execution Test with Claude
+
+on:
+ push:
+ branches: [ main ]
+ paths:
+ - 'docs/tutorial.rst'
+ - 'docs/_code/**'
+ - '.github/workflows/tutorial-execution-test.yml'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - 'docs/tutorial.rst'
+ - 'docs/_code/**'
+ - '.github/workflows/tutorial-execution-test.yml'
+ workflow_dispatch: # Allow manual trigger
+
+jobs:
+ execute-tutorial:
+ name: Execute Tutorial with Claude
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Python and Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '18'
+
+ - name: Install Anthropic SDK
+ run: npm install @anthropic-ai/sdk
+
+ - name: Set up Python and PDM
+ uses: pdm-project/setup-pdm@v3
+ with:
+ python-version: '3.9'
+ cache: true
+ cache-dependency-path: "**/pyproject.toml"
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y gtkwave
+ pdm install
+
+ - name: Create tutorial execution script
+ run: |
+ cat > execute_tutorial.js << 'EOF'
+ const fs = require('fs');
+ const path = require('path');
+ const { exec, execSync } = require('child_process');
+ const Anthropic = require('@anthropic-ai/sdk');
+ const util = require('util');
+ const execAsync = util.promisify(exec);
+
+ // Helper function to get code from a file reference
+ function getCodeFromFileRef(fileRef) {
+ const filePath = path.join('docs', fileRef);
+ if (fs.existsSync(filePath)) {
+ return fs.readFileSync(filePath, 'utf8');
+ }
+ return null;
+ }
+
+ // Initialize Anthropic client
+ const anthropic = new Anthropic({
+ apiKey: process.env.ANTHROPIC_API_KEY,
+ });
+
+ async function executeTutorial() {
+ // Read the tutorial content
+ const tutorialContent = fs.readFileSync('docs/tutorial.rst', 'utf8');
+
+ // First, have Claude analyze the tutorial and extract executable steps
+ const analysisPrompt = `
+ ${tutorialContent}
+
+
+ You are an expert in hardware design, HDLs, and Python. Please analyze the above Amaranth HDL tutorial (in RST format) and extract a step-by-step execution plan.
+
+ Note that this is a Sphinx RST file, with code examples in these forms:
+ 1. Inline code blocks (marked with .. code-block:: python)
+ 2. File includes (marked with .. literalinclude:: _code/filename.py)
+
+ For each executable code example in the tutorial:
+ 1. Identify the filename it should be saved as (from literalinclude or reasonable name for code blocks)
+ 2. Extract the exact code needed for execution
+ 3. Identify any dependencies or prerequisites needed to run this code
+ 4. Describe what the expected output or result should be
+
+ Format your response in JSON like this:
+ {
+ "steps": [
+ {
+ "name": "Step description",
+ "file": "filename.py",
+ "code": "Python code goes here",
+ "dependencies": ["list", "of", "dependencies"],
+ "expected_result": "Description of expected output",
+ "validation": "How to verify it worked correctly"
+ }
+ ]
+ }
+
+ Only include steps that involve executing code. Focus on extracting the examples exactly as shown.`;
+
+ try {
+ console.log("Analyzing tutorial to extract executable steps...");
+
+ // Call Claude to analyze the tutorial
+ const analysisResponse = await anthropic.messages.create({
+ model: "claude-3-sonnet-20240229",
+ max_tokens: 4000,
+ messages: [
+ { role: "user", content: analysisPrompt }
+ ],
+ temperature: 0.2,
+ });
+
+ // Parse Claude's response to get the execution plan
+ const analysisText = analysisResponse.content[0].text;
+
+ // Extract JSON from Claude's response
+ const jsonMatch = analysisText.match(/\{[\s\S]*\}/);
+ if (!jsonMatch) {
+ throw new Error("Could not extract JSON execution plan from Claude's response");
+ }
+
+ const executionPlan = JSON.parse(jsonMatch[0]);
+ fs.writeFileSync('execution_plan.json', JSON.stringify(executionPlan, null, 2));
+ console.log(`Extracted ${executionPlan.steps.length} executable steps from tutorial`);
+
+ // Execute each step in the plan
+ const results = [];
+
+ for (let i = 0; i < executionPlan.steps.length; i++) {
+ const step = executionPlan.steps[i];
+ console.log(`\n==== Executing Step ${i+1}: ${step.name} ====`);
+
+ // Check if we have this file already in docs/_code
+ const docFilePath = path.join('docs', '_code', step.file);
+ if (fs.existsSync(docFilePath)) {
+ // Use the existing file from docs/_code
+ const codeFromFile = fs.readFileSync(docFilePath, 'utf8');
+ fs.writeFileSync(step.file, codeFromFile);
+ console.log(`Using existing file from docs/_code/${step.file}`);
+ } else {
+ // Save the code to a file as extracted by Claude
+ fs.writeFileSync(step.file, step.code);
+ console.log(`Created file from extraction: ${step.file}`);
+ }
+
+ // Execute the code
+ try {
+ console.log(`Running: pdm run python ${step.file}`);
+ const { stdout, stderr } = await execAsync(`pdm run python ${step.file}`, { timeout: 60000 });
+
+ // Record the result
+ results.push({
+ step: i+1,
+ name: step.name,
+ file: step.file,
+ success: true,
+ stdout,
+ stderr,
+ error: null
+ });
+
+ console.log("Output:", stdout);
+ if (stderr) console.error("Errors:", stderr);
+
+ } catch (error) {
+ console.error(`Error executing ${step.file}:`, error.message);
+
+ // Record the failure
+ results.push({
+ step: i+1,
+ name: step.name,
+ file: step.file,
+ success: false,
+ stdout: error.stdout || "",
+ stderr: error.stderr || "",
+ error: error.message
+ });
+ }
+ }
+
+ // Save the execution results
+ fs.writeFileSync('execution_results.json', JSON.stringify(results, null, 2));
+
+ // Have Claude analyze the results
+ const resultsPrompt = `
+ I've executed the code examples from an Amaranth HDL tutorial. Here are the results:
+
+ ${JSON.stringify(results, null, 2)}
+
+ Please analyze these results and provide:
+
+ 1. A summary of which examples worked and which failed
+ 2. For failed examples, analyze what might have gone wrong based on error messages
+ 3. Suggest possible improvements to the tutorial based on execution results
+ 4. Overall assessment of the tutorial's executability for beginners
+
+ Format your response with clear headings and bullet points.`;
+
+ console.log("\nAnalyzing execution results with Claude...");
+
+ const resultsAnalysisResponse = await anthropic.messages.create({
+ model: "claude-3-sonnet-20240229",
+ max_tokens: 4000,
+ messages: [
+ { role: "user", content: resultsPrompt }
+ ],
+ temperature: 0.2,
+ });
+
+ // Save Claude's analysis of the results
+ fs.writeFileSync('tutorial_execution_analysis.md', resultsAnalysisResponse.content[0].text);
+ console.log("Analysis complete. Results written to tutorial_execution_analysis.md");
+
+ console.log("\n=== SUMMARY OF EXECUTION ANALYSIS ===\n");
+ console.log(resultsAnalysisResponse.content[0].text.substring(0, 1000) + "...");
+
+ } catch (error) {
+ console.error("Error during execution:", error);
+ process.exit(1);
+ }
+ }
+
+ executeTutorial();
+ EOF
+
+ chmod +x execute_tutorial.js
+
+ - name: Check ANTHROPIC_API_KEY is set
+ id: check_api_key
+ run: |
+ if [ -n "${{ secrets.ANTHROPIC_API_KEY }}" ]; then
+ echo "API key is set, proceeding with Claude execution test"
+ echo "has_api_key=true" >> $GITHUB_OUTPUT
+ else
+ echo "ANTHROPIC_API_KEY is not set. Skipping Claude-based execution."
+ echo "has_api_key=false" >> $GITHUB_OUTPUT
+ echo "## ⚠️ Warning - Claude Execution Test Skipped" >> $GITHUB_STEP_SUMMARY
+ echo "* ANTHROPIC_API_KEY secret is not configured in this repository" >> $GITHUB_STEP_SUMMARY
+ echo "* Execution test will be skipped for now" >> $GITHUB_STEP_SUMMARY
+ echo "* This test will run automatically once the secret is configured" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ - name: Execute tutorial with Claude
+ if: steps.check_api_key.outputs.has_api_key == 'true'
+ env:
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ run: node execute_tutorial.js
+
+ - name: Archive execution results
+ if: steps.check_api_key.outputs.has_api_key == 'true'
+ uses: actions/upload-artifact@v4
+ with:
+ name: tutorial-execution-results
+ path: |
+ *.py
+ *.v
+ *.vcd
+ execution_plan.json
+ execution_results.json
+ tutorial_execution_analysis.md
\ No newline at end of file
diff --git a/.github/workflows/tutorial-test.yml b/.github/workflows/tutorial-test.yml
new file mode 100644
index 000000000..8e83d2d66
--- /dev/null
+++ b/.github/workflows/tutorial-test.yml
@@ -0,0 +1,141 @@
+name: Tutorial Code Test
+
+on:
+ push:
+ branches: [ main ]
+ paths:
+ - 'docs/tutorial.rst'
+ - 'docs/_code/**'
+ - '.github/workflows/tutorial-test.yml'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - 'docs/tutorial.rst'
+ - 'docs/_code/**'
+ - '.github/workflows/tutorial-test.yml'
+ workflow_dispatch: # Allow manual trigger
+
+jobs:
+ validate-tutorial:
+ name: Validate Tutorial Content
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Check tutorial refers to existing files
+ run: |
+ # Extract code filename references from tutorial.rst
+ TUTORIAL_FILES=$(grep -o "_code/[a-zA-Z_]*\.py" docs/tutorial.rst | cut -d'/' -f2 | sort | uniq)
+
+ echo "Files mentioned in tutorial.rst:"
+ echo "$TUTORIAL_FILES"
+
+ # Check if each mentioned file exists in docs/_code/
+ for file in $TUTORIAL_FILES; do
+ if [ ! -f "docs/_code/$file" ]; then
+ echo "Error: $file mentioned in tutorial.rst but missing from docs/_code/"
+ exit 1
+ else
+ echo "✓ Found docs/_code/$file"
+ fi
+ done
+
+ test-tutorial:
+ name: Test Tutorial Code
+ runs-on: ubuntu-latest
+ needs: validate-tutorial
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Python and PDM
+ uses: pdm-project/setup-pdm@v3
+ with:
+ python-version: '3.9'
+ cache: true
+ cache-dependency-path: "**/pyproject.toml"
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y gtkwave
+ pdm install
+
+ - name: Run and_gate.py example
+ run: |
+ pdm run python docs/_code/and_gate.py
+ ls -la and_gate.v && head -n 10 and_gate.v
+
+ - name: Run blinky.py example
+ run: |
+ pdm run python docs/_code/blinky.py
+ ls -la blinky.vcd
+
+ - name: Run up_counter.py example
+ run: |
+ pdm run python docs/_code/up_counter.py
+ ls -la counter.v && head -n 10 counter.v
+
+ - name: Run uart_receiver.py example
+ run: |
+ pdm run python docs/_code/uart_receiver.py
+ ls -la uart_rx.v && head -n 10 uart_rx.v
+
+ - name: Run uart_sim.py example
+ run: |
+ pdm run python docs/_code/uart_sim.py
+ ls -la uart_sim.vcd
+
+ - name: Run controlled_blinker.py example
+ run: |
+ pdm run python docs/_code/controlled_blinker.py
+ ls -la blinker_system.v && head -n 10 blinker_system.v
+ ls -la blinker_system.vcd
+
+ - name: Verify waveform files with GTKWave
+ run: |
+ gtkwave -V
+ for vcd_file in *.vcd; do
+ if [ -f "$vcd_file" ]; then
+ echo "Verifying $vcd_file with GTKWave..."
+ gtkwave -V "$vcd_file" || true
+ fi
+ done
+
+ - name: Generate test summary
+ run: |
+ echo "## Tutorial Code Test Results" > summary.md
+ echo "| Example | Status |" >> summary.md
+ echo "|---------|--------|" >> summary.md
+
+ check_file() {
+ if [ -f "$1" ]; then
+ echo "| $2 | ✅ Pass |" >> summary.md
+ else
+ echo "| $2 | ❌ Fail |" >> summary.md
+ EXIT_CODE=1
+ fi
+ }
+
+ EXIT_CODE=0
+ check_file "and_gate.v" "AND Gate"
+ check_file "blinky.vcd" "LED Blinker"
+ check_file "counter.v" "Up Counter"
+ check_file "uart_rx.v" "UART Receiver"
+ check_file "uart_sim.vcd" "UART Simulation"
+ check_file "blinker_system.v" "Controlled Blinker"
+ check_file "blinker_system.vcd" "Blinker Simulation"
+
+ cat summary.md
+ exit $EXIT_CODE
+
+ - name: Archive generated files
+ uses: actions/upload-artifact@v4
+ with:
+ name: tutorial-outputs
+ path: |
+ *.v
+ *.vcd
+ summary.md
\ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..a12379544
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,33 @@
+# Amaranth HDL Development Guide
+
+## Build and Test Commands
+```bash
+# Install dependencies
+pdm install
+
+# Run all tests
+pdm run test
+
+# Run code tests only
+pdm run test-code
+
+# Run a single test
+python -m unittest tests.test_module.TestClass.test_method
+
+# Run documentation tests
+pdm run test-docs
+
+# Generate coverage reports
+pdm run coverage-text
+pdm run coverage-html
+```
+
+## Code Style Guidelines
+- **Imports**: Group by standard library, then project imports
+- **Naming**: Classes use PascalCase, functions/methods use snake_case, constants use UPPER_SNAKE_CASE
+- **Types**: Type hints are minimal but encouraged in new code
+- **Testing**: Test classes extend FHDLTestCase from tests/utils.py
+- **Assertions**: Use self.assertEqual(), self.assertRaisesRegex(), etc. in tests
+- **Error Handling**: Use ValueError/TypeError with descriptive messages for validation
+- **Documentation**: Use Sphinx-compatible docstrings for all public APIs
+- **Formatting**: 4-space indentation, 100-character line limit recommended
\ No newline at end of file
diff --git a/docs/_code/and_gate.py b/docs/_code/and_gate.py
new file mode 100644
index 000000000..6cbd401db
--- /dev/null
+++ b/docs/_code/and_gate.py
@@ -0,0 +1,27 @@
+from amaranth import *
+from amaranth.back import verilog
+
+class AndGate(Elaboratable):
+ def __init__(self):
+ # Input ports
+ self.a = Signal()
+ self.b = Signal()
+ # Output port
+ self.y = Signal()
+
+ def elaborate(self, platform):
+ # The 'elaborate' method builds the actual circuit
+ m = Module()
+ # y = a AND b
+ m.d.comb += self.y.eq(self.a & self.b)
+ return m
+
+# Create the gate
+gate = AndGate()
+
+# Generate Verilog (for viewing or using with other tools)
+with open("and_gate.v", "w") as f:
+ f.write(verilog.convert(gate, ports=[gate.a, gate.b, gate.y]))
+
+# How to run: pdm run python and_gate.py
+# This will generate and_gate.v
\ No newline at end of file
diff --git a/docs/_code/blinky.py b/docs/_code/blinky.py
new file mode 100644
index 000000000..ff3cfa639
--- /dev/null
+++ b/docs/_code/blinky.py
@@ -0,0 +1,51 @@
+from amaranth import *
+
+class Blinky(Elaboratable):
+ def __init__(self):
+ # No parameters needed for this simple example
+ pass
+
+ def elaborate(self, platform):
+ # The 'elaborate' method transforms our Python description into a hardware circuit
+
+ # Get the LED from the platform (if running on actual hardware)
+ if platform is not None:
+ led = platform.request("led", 0)
+ else:
+ # For simulation, create a dummy LED signal
+ led = Signal()
+
+ # Create a timer (24-bit counter)
+ # This will count from 0 to 2^24-1 and then wrap around
+ timer = Signal(24)
+
+ m = Module()
+
+ # Increment timer every clock cycle
+ # 'd.sync' means this happens on the rising edge of the clock
+ m.d.sync += timer.eq(timer + 1)
+
+ # Connect LED to the most significant bit of the timer
+ # timer[-1] means "the last bit" (most significant bit)
+ # This makes the LED toggle on/off when the timer overflows
+ m.d.comb += led.o.eq(timer[-1])
+
+ return m
+
+# This lets us run this file directly or include it in other scripts
+if __name__ == "__main__":
+ from amaranth.sim import Simulator, Period
+
+ # Create our circuit
+ dut = Blinky()
+
+ # Set up a simple simulation to watch the LED blink
+ sim = Simulator(dut)
+ sim.add_clock(Period(MHz=1)) # 1 MHz clock (1μs period)
+
+ # Run simulation and generate a waveform file
+ with sim.write_vcd("blinky.vcd"):
+ sim.run_until(100 * 1_000_000) # Run for 100ms of simulated time
+
+# How to run: pdm run python blinky.py
+# This will generate blinky.vcd, which you can view with GTKWave
\ No newline at end of file
diff --git a/docs/_code/controlled_blinker.py b/docs/_code/controlled_blinker.py
new file mode 100644
index 000000000..3a3c5c5af
--- /dev/null
+++ b/docs/_code/controlled_blinker.py
@@ -0,0 +1,86 @@
+from amaranth import *
+from amaranth.lib import wiring
+from amaranth.lib.wiring import In, Out
+
+# Import our UpCounter
+from up_counter import UpCounter
+
+class ControlledBlinker(Elaboratable):
+ """
+ An LED blinker that uses a counter to control blink rate.
+ """
+ def __init__(self, freq_hz=1):
+ """
+ Create a blinker with specified frequency.
+
+ Args:
+ freq_hz: Blink frequency in Hz (defaults to 1Hz)
+ """
+ self.freq_hz = freq_hz
+
+ def elaborate(self, platform):
+ m = Module()
+
+ # Get system clock frequency (for actual hardware)
+ if platform is not None:
+ sys_clock_freq = platform.default_clk_frequency
+ else:
+ # For simulation, assume 1MHz clock
+ sys_clock_freq = 1_000_000
+
+ # Calculate counter limit based on desired blink frequency
+ # The counter will overflow twice per cycle (on-off)
+ counter_limit = int(sys_clock_freq / (2 * self.freq_hz)) - 1
+
+ # Create our counter submodule
+ counter = UpCounter(counter_limit)
+ # Add it to our module with a name
+ m.submodules.counter = counter
+
+ # Create a toggle flip-flop for LED state
+ led_state = Signal(1)
+
+ # Always enable the counter
+ m.d.comb += counter.en.eq(1)
+
+ # Toggle LED state on overflow
+ with m.If(counter.ovf):
+ m.d.sync += led_state.eq(~led_state)
+
+ # Connect to the LED if running on hardware
+ if platform is not None:
+ led = platform.request("led", 0)
+ m.d.comb += led.o.eq(led_state)
+
+ return m
+
+# Example usage
+if __name__ == "__main__":
+ from amaranth.sim import Simulator, Period
+
+ # Create a 2Hz blinker
+ dut = ControlledBlinker(freq_hz=2)
+
+ # Basic simulation to observe blinking
+ sim = Simulator(dut)
+ sim.add_clock(Period(MHz=1)) # 1MHz system clock
+
+ # Add a simple test to just run for a while
+ def test_bench():
+ pass
+
+ sim.add_process(test_bench)
+
+ # Run for 2 seconds (enough to see multiple blinks at 2Hz)
+ with sim.write_vcd("blinker_system.vcd", "blinker_system.gtkw"):
+ sim.run_until(2_000_000) # 2M ns = 2 seconds
+
+ print("Simulation complete. View waveform with 'gtkwave blinker_system.vcd'")
+
+ # Generate Verilog
+ from amaranth.back import verilog
+
+ with open("blinker_system.v", "w") as f:
+ f.write(verilog.convert(dut))
+
+ print("Generated blinker_system.v")
\ No newline at end of file
diff --git a/docs/_code/program_icestick.py b/docs/_code/program_icestick.py
new file mode 100644
index 000000000..5146ada14
--- /dev/null
+++ b/docs/_code/program_icestick.py
@@ -0,0 +1,22 @@
+from amaranth import *
+# This import assumes you have amaranth-boards package installed
+# If using a different board, import the appropriate platform
+try:
+ from amaranth_boards.icestick import ICEStickPlatform
+
+ # Import our blinker
+ from controlled_blinker import ControlledBlinker
+
+ # Create a platform for the iCEStick board
+ platform = ICEStickPlatform()
+
+ # Create a 1Hz blinker (adjust frequency as needed)
+ blinker = ControlledBlinker(freq_hz=1)
+
+ # Build and program
+ platform.build(blinker, do_program=True)
+except ImportError:
+ print("This example requires amaranth-boards package")
+ print("Install it with: pdm add amaranth-boards")
+
+# How to run: pdm run python program_icestick.py
\ No newline at end of file
diff --git a/docs/_code/uart_receiver.py b/docs/_code/uart_receiver.py
new file mode 100644
index 000000000..e0b938df1
--- /dev/null
+++ b/docs/_code/uart_receiver.py
@@ -0,0 +1,121 @@
+from amaranth import *
+from amaranth.lib import wiring
+from amaranth.lib.wiring import In, Out
+
+class UARTReceiver(wiring.Component):
+ """
+ A UART (serial) receiver that converts serial data to parallel.
+
+ UART uses start and stop bits to frame each byte:
+ - Line is high when idle
+ - Start bit is low (0)
+ - 8 data bits follow
+ - Stop bit is high (1)
+
+ Parameters
+ ----------
+ divisor : int
+ Clock divisor for baud rate (system_clock / baud_rate)
+ Example: 100MHz system clock, 9600 baud → divisor = 10,417
+
+ Attributes
+ ----------
+ i : Signal, in
+ Serial input line
+ ack : Signal, in
+ Acknowledgment (read the received byte)
+ data : Signal, out
+ 8-bit received data
+ rdy : Signal, out
+ Data ready flag (high when byte received)
+ err : Signal, out
+ Error flag (high on framing error)
+ """
+
+ # Interface
+ i: In(1) # Input bit (serial line)
+ data: Out(8) # Received byte (parallel output)
+ rdy: Out(1) # Data ready flag
+ ack: In(1) # Acknowledgment
+ err: Out(1) # Error flag
+
+ def __init__(self, divisor):
+ super().__init__()
+ self.divisor = divisor # Clock divisor for baud rate
+
+ def elaborate(self, platform):
+ m = Module()
+
+ # Baud rate generator
+ # This creates a "strobe" (stb) that pulses once per bit period
+ ctr = Signal(range(self.divisor)) # Counter for clock division
+ stb = Signal() # Strobe signal (pulses when we should sample)
+
+ # When counter reaches zero, reset it and pulse the strobe
+ with m.If(ctr == 0):
+ m.d.sync += ctr.eq(self.divisor - 1) # Reset counter
+ m.d.comb += stb.eq(1) # Pulse strobe
+ with m.Else():
+ m.d.sync += ctr.eq(ctr - 1) # Decrement counter
+
+ # Bit counter (counts 8 data bits)
+ bit = Signal(3) # 3 bits to count 0-7
+
+ # FSM (Finite State Machine) for UART reception
+ with m.FSM() as fsm:
+ # Initial state: wait for start bit
+ with m.State("START"):
+ with m.If(~self.i): # If input goes low (start bit detected)
+ m.next = "DATA" # Move to DATA state
+ m.d.sync += [
+ # Sample in middle of bit by setting counter to half divisor
+ ctr.eq(self.divisor // 2),
+ # Prepare to receive 8 bits (bit 7 down to bit 0)
+ bit.eq(7),
+ ]
+
+ # Receiving data bits
+ with m.State("DATA"):
+ with m.If(stb): # On each baud strobe (sampling point)
+ m.d.sync += [
+ bit.eq(bit - 1), # Decrement bit counter
+ # Cat() concatenates bits - this shifts received bit into the data
+ self.data.eq(Cat(self.i, self.data[:-1])),
+ ]
+ with m.If(bit == 0): # If all bits received
+ m.next = "STOP" # Move to STOP state
+
+ # Check stop bit
+ with m.State("STOP"):
+ with m.If(stb): # On baud strobe
+ with m.If(self.i): # If input is high (valid stop bit)
+ m.next = "DONE" # Move to DONE state
+ with m.Else(): # If input is low (invalid stop bit)
+ m.next = "ERROR" # Move to ERROR state
+
+ # Data ready - wait for acknowledgment
+ with m.State("DONE"):
+ m.d.comb += self.rdy.eq(1) # Set ready flag
+ with m.If(self.ack): # When acknowledged
+ m.next = "START" # Go back to START for next byte
+
+ # Error state - stay here until reset
+ # fsm.ongoing() checks if FSM is in a specific state
+ m.d.comb += self.err.eq(fsm.ongoing("ERROR"))
+ with m.State("ERROR"):
+ pass # Do nothing (stay in error state)
+
+ return m
+
+# Example usage
+if __name__ == "__main__":
+ from amaranth.back import verilog
+
+ # Create a UART receiver for 9600 baud with a 1MHz clock
+ uart = UARTReceiver(divisor=104) # 1,000,000 / 9600 ≈ 104
+
+ # Generate Verilog
+ with open("uart_rx.v", "w") as f:
+ f.write(verilog.convert(uart))
+
+ print("Generated uart_rx.v")
\ No newline at end of file
diff --git a/docs/_code/uart_sim.py b/docs/_code/uart_sim.py
new file mode 100644
index 000000000..a6610be30
--- /dev/null
+++ b/docs/_code/uart_sim.py
@@ -0,0 +1,93 @@
+from amaranth import *
+from amaranth.sim import Simulator, Period
+# Import our UART receiver
+from uart_receiver import UARTReceiver
+
+# Create our device under test
+dut = UARTReceiver(divisor=4) # Small divisor for faster simulation
+
+async def uart_tx(ctx, byte, divisor):
+ """Helper function to transmit a byte over UART."""
+ ctx.set(dut.i, 1) # Idle high
+ await ctx.tick()
+
+ # Start bit (low)
+ ctx.set(dut.i, 0)
+ for _ in range(divisor):
+ await ctx.tick()
+
+ # 8 data bits, LSB first
+ for i in range(8):
+ bit = (byte >> i) & 1
+ ctx.set(dut.i, bit)
+ for _ in range(divisor):
+ await ctx.tick()
+
+ # Stop bit (high)
+ ctx.set(dut.i, 1)
+ for _ in range(divisor):
+ await ctx.tick()
+
+async def test_bench(ctx):
+ # Initialize signals
+ ctx.set(dut.i, 1) # Idle high
+ ctx.set(dut.ack, 0) # No acknowledgment
+
+ # Wait a few cycles
+ for _ in range(10):
+ await ctx.tick()
+
+ # Send byte 0xA5 (10100101)
+ await uart_tx(ctx, 0xA5, dut.divisor)
+
+ # Wait for ready signal
+ for _ in range(10):
+ await ctx.tick()
+ if ctx.get(dut.rdy):
+ break
+
+ # Verify received data
+ assert ctx.get(dut.rdy), "Ready signal not asserted"
+ assert ctx.get(dut.data) == 0xA5, f"Wrong data: {ctx.get(dut.data):02x} (expected 0xA5)"
+
+ # Acknowledge reception
+ ctx.set(dut.ack, 1)
+ await ctx.tick()
+ ctx.set(dut.ack, 0)
+
+ # Send another byte with a framing error (no stop bit)
+ ctx.set(dut.i, 1) # Idle high
+ await ctx.tick()
+
+ # Start bit
+ ctx.set(dut.i, 0)
+ for _ in range(dut.divisor):
+ await ctx.tick()
+
+ # 8 data bits, all 1s
+ for _ in range(8):
+ ctx.set(dut.i, 1)
+ for _ in range(dut.divisor):
+ await ctx.tick()
+
+ # Incorrect stop bit (should be 1, sending 0)
+ ctx.set(dut.i, 0)
+ for _ in range(dut.divisor):
+ await ctx.tick()
+
+ # Wait a bit and check error flag
+ for _ in range(10):
+ await ctx.tick()
+
+ assert ctx.get(dut.err), "Error flag not asserted on framing error"
+
+# Set up the simulator
+sim = Simulator(dut)
+sim.add_clock(Period(MHz=1))
+sim.add_testbench(test_bench)
+
+# Run simulation
+with sim.write_vcd("uart_sim.vcd", "uart_sim.gtkw"):
+ sim.run()
+
+print("Simulation complete. View the waveform with 'gtkwave uart_sim.vcd'")
\ No newline at end of file
diff --git a/docs/_code/up_counter.py b/docs/_code/up_counter.py
index 5a4783a57..f537a5191 100644
--- a/docs/_code/up_counter.py
+++ b/docs/_code/up_counter.py
@@ -78,4 +78,4 @@ async def bench(ctx):
top = UpCounter(25)
with open("up_counter.v", "w") as f:
- f.write(verilog.convert(top))
+ f.write(verilog.convert(top))
\ No newline at end of file
diff --git a/docs/tools/README.md b/docs/tools/README.md
new file mode 100644
index 000000000..752e574ac
--- /dev/null
+++ b/docs/tools/README.md
@@ -0,0 +1,33 @@
+# Documentation Tools
+
+This directory contains utility scripts for maintaining Amaranth documentation.
+
+## RST Formatting Tools
+
+### `fix_rst_underlines.py`
+
+Automatically fixes RST title underlines to match the length of their title text.
+
+Usage:
+```bash
+pdm run python docs/tools/fix_rst_underlines.py docs/file.rst
+```
+
+### `fix_rst_bullet_lists.py`
+
+Ensures all bullet lists in RST files end with a blank line, which is required by the RST parser.
+
+Usage:
+```bash
+pdm run python docs/tools/fix_rst_bullet_lists.py docs/file.rst
+```
+
+## Using These Tools
+
+These tools are helpful when you encounter warnings during documentation builds. You can run them on RST files
+to automatically fix common formatting issues.
+
+Example workflow:
+1. Run `pdm run document` and observe formatting warnings
+2. Run the appropriate fix script(s) on the problematic files
+3. Verify the fixes with `pdm run document` again
\ No newline at end of file
diff --git a/docs/tools/fix_rst_bullet_lists.py b/docs/tools/fix_rst_bullet_lists.py
new file mode 100644
index 000000000..a9f8d2382
--- /dev/null
+++ b/docs/tools/fix_rst_bullet_lists.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+"""
+Script to fix RST bullet list formatting in tutorial.rst
+This ensures all bullet lists end with a blank line.
+"""
+
+import re
+import sys
+
+def fix_rst_bullet_lists(filename):
+ """Fix bullet lists in the given RST file."""
+ with open(filename, 'r') as f:
+ lines = f.readlines()
+
+ fixed_lines = []
+ i = 0
+
+ while i < len(lines):
+ # Add the current line to our output
+ fixed_lines.append(lines[i])
+
+ # Check if this line starts a bullet point
+ if lines[i].strip().startswith('- '):
+ # Find the end of the bullet list
+ j = i + 1
+
+ # Look for more bullet points that continue the list
+ while j < len(lines) and (lines[j].strip().startswith('- ') or lines[j].strip().startswith(' ')):
+ fixed_lines.append(lines[j])
+ j = j + 1
+
+ # If the next line after the list isn't empty, add a blank line
+ if j < len(lines) and lines[j].strip() != '':
+ print(f"Adding blank line after bullet list at line {i+1}")
+ fixed_lines.append('\n')
+
+ i = j
+ else:
+ i += 1
+
+ # Write the fixed content back to the file
+ with open(filename, 'w') as f:
+ f.writelines(fixed_lines)
+
+ print(f"Fixed bullet lists in {filename}")
+
+if __name__ == "__main__":
+ if len(sys.argv) != 2:
+ print(f"Usage: {sys.argv[0]} ")
+ sys.exit(1)
+
+ fix_rst_bullet_lists(sys.argv[1])
\ No newline at end of file
diff --git a/docs/tools/fix_rst_underlines.py b/docs/tools/fix_rst_underlines.py
new file mode 100644
index 000000000..b83a892ad
--- /dev/null
+++ b/docs/tools/fix_rst_underlines.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+"""
+Script to automatically fix RST title underlines in tutorial.rst
+This ensures all underlines match the length of their title text.
+"""
+
+import re
+import sys
+
+def fix_rst_underlines(filename):
+ """Fix all RST title underlines in the given file."""
+ with open(filename, 'r') as f:
+ content = f.read()
+
+ # Pattern to match a title line followed by an underline
+ # Group 1 = Title text
+ # Group 2 = Underline character (= or - or ~)
+ # Group 3 = Full underline
+ pattern = r'([^\n]+)\n([=\-~]+)'
+
+ def replace_underline(match):
+ title = match.group(1)
+ underline_char = match.group(2)[0] # Get the first character of the underline
+ # Create a new underline with the same length as the title
+ new_underline = underline_char * len(title)
+
+ # Check if it's already correct
+ if match.group(2) == new_underline:
+ # If already correct, no change
+ return match.group(0)
+
+ # Report the change
+ print(f"Fixing: '{title}'\n Old: {match.group(2)}\n New: {new_underline}")
+
+ # Return the title with the fixed underline
+ return f"{title}\n{new_underline}"
+
+ # Replace all underlines with correct length ones
+ fixed_content = re.sub(pattern, replace_underline, content)
+
+ # Write the fixed content back to the file
+ with open(filename, 'w') as f:
+ f.write(fixed_content)
+
+ print(f"Fixed underlines in {filename}")
+
+if __name__ == "__main__":
+ if len(sys.argv) != 2:
+ print(f"Usage: {sys.argv[0]} ")
+ sys.exit(1)
+
+ fix_rst_underlines(sys.argv[1])
\ No newline at end of file
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index 4f789495a..e148d86a4 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -1,12 +1,371 @@
Tutorial
========
-.. todo::
+This tutorial will guide you through using Amaranth HDL, starting with simple circuits and progressing to more complex designs.
- The official tutorial is still being written. Until it's ready, consider following one of the tutorials written by the Amaranth community:
+Introduction to Amaranth HDL
+----------------------------
- * `Learning FPGA Design with nMigen `_ by Vivonomicon;
- * `"I want to learn nMigen" `_ by kbob;
- * `A tutorial for using Amaranth HDL `_ by Robert Baruch.
- * `Graded exercises for Amaranth HDL `_ by Robert Baruch.
- * `My journey with the Amaranth HDL `_ by David Sporn, focussed on setting up the workstation, using formal verification and setting up continuous integration.
+Amaranth is a Python-based hardware description language (HDL) that allows you to design digital circuits using Python's object-oriented features. It provides a more modern and productive alternative to traditional HDLs like Verilog or VHDL.
+
+What is HDL?
+~~~~~~~~~~~~
+
+A Hardware Description Language is a specialized programming language used to describe the structure and behavior of electronic circuits. Unlike software programming, HDL code describes actual physical hardware structures that will be created on an FPGA or ASIC.
+
+Why Amaranth?
+~~~~~~~~~~~~~
+
+- **Python-based** - Use a familiar language with modern features
+
+----------------------------------------------------------------- **Object-oriented** - Create reusable components
+------------------------------------------------- **Built-in testing** - Simulate designs without external tools
+--------------------------------------------------------------- **Powerful abstractions** - Simplify common hardware patterns
+
+Setting Up
+----------
+
+Prerequisites
+~~~~~~~~~~~~~
+
+Before starting, you'll need:
+
+- Python 3.9 or newer installed
+
+------------------------------- Basic knowledge of Python
+-------------------------- For synthesis to hardware: Yosys (optional, installed automatically with PDM)
+
+Installation
+~~~~~~~~~~~~
+
+Install Amaranth using PDM (Python Development Master), which will handle creating a virtual environment for you:
+
+.. code-block:: bash
+
+ # Install PDM if you don't have it
+ pip install pdm
+
+ # Clone the repository and navigate to it
+ git clone https://github.com/amaranth-lang/amaranth.git
+ cd amaranth
+
+ # Install Amaranth and its dependencies in a virtual environment
+ pdm install
+
+To run Amaranth scripts, use PDM to ensure your code runs in the correct environment:
+
+.. code-block:: bash
+
+ pdm run python your_script.py
+
+Understanding Digital Logic Basics
+----------------------------------
+
+Signals
+~~~~~~~
+
+Signals are the fundamental elements in digital circuits - they represent wires carrying data.
+
+.. code-block:: python
+
+ from amaranth import *
+
+ # Create a module (a container for your circuit)
+ m = Module()
+
+ # Create signals (these represent wires in your circuit)
+ a = Signal() # 1-bit signal (can be 0 or 1)
+ b = Signal(8) # 8-bit signal (can be 0-255)
+ c = Signal(8, init=42) # 8-bit signal with initial value 42
+
+ # Connect signals (using combinational logic)
+ m.d.comb += c.eq(b + 1) # c will always equal b + 1
+
+Clock Domains
+~~~~~~~~~~~~~
+
+Digital circuits operate based on clock signals. Amaranth uses clock domains to organize logic:
+
+- **Combinational domain** (``m.d.comb``): Logic that responds immediately to input changes
+
+------------------------------------------------------------------------------------------- **Synchronous domain** (``m.d.sync``): Logic that updates only on clock edges
+
+.. code-block:: python
+
+ # Combinational assignment (happens continuously)
+ m.d.comb += output.eq(input_a & input_b) # output = input_a AND input_b
+
+ # Synchronous assignment (happens only on clock edges)
+ m.d.sync += counter.eq(counter + 1) # counter increments each clock cycle
+
+Basic Example: AND Gate
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Let's create a simple AND gate:
+
+.. literalinclude:: _code/and_gate.py
+ :caption: and_gate.py
+ :linenos:
+
+Viewing the generated Verilog (``and_gate.v``) shows what hardware will be created:
+
+.. code-block:: verilog
+
+ module top(a, b, y);
+ input a;
+ input b;
+ output y;
+ assign y = (a & b);
+ endmodule
+
+Your First Circuit: LED Blinker
+-------------------------------
+
+Now let's create a more practical circuit that blinks an LED:
+
+.. literalinclude:: _code/blinky.py
+ :caption: blinky.py
+ :linenos:
+
+Understanding the Code
+~~~~~~~~~~~~~~~~~~~~~~
+
+- **Elaboratable**: Base class for all Amaranth circuits
+
+-------------------------------------------------------- **elaborate(self, platform)**: Method that builds the actual circuit
+--------------------------------------------------------------------- **Signal(24)**: Creates a 24-bit counter that can count from 0 to 2^24-1
+------------------------------------------------------------------------- **m.d.sync += timer.eq(timer + 1)**: Increments the timer on each clock edge
+----------------------------------------------------------------------------- **timer[-1]**: Accesses the most significant bit (bit 23) of the timer
+----------------------------------------------------------------------- **led.o.eq()**: Connects the output pin of the LED to our signal
+
+Running on Hardware
+~~~~~~~~~~~~~~~~~~~
+
+To run on actual FPGA hardware, you'd need to specify a platform and call the build method:
+
+.. code-block:: python
+
+ # Example for specific hardware (requires amaranth-boards package)
+ from amaranth_boards.icestick import ICEStickPlatform
+
+ if __name__ == "__main__":
+ platform = ICEStickPlatform()
+ platform.build(Blinky(), do_program=True)
+
+Viewing Simulation Results
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The simulation generates a VCD (Value Change Dump) file that you can view with waveform viewer software:
+
+1. Install GTKWave: http://gtkwave.sourceforge.net/
+2. Open the generated VCD file: ``gtkwave blinky.vcd``
+3. Select signals to view in the waveform
+
+Components with Interfaces: Up Counter
+--------------------------------------
+
+Now let's create a reusable component with a well-defined interface:
+
+.. literalinclude:: _code/up_counter.py
+ :caption: up_counter.py
+ :linenos:
+ :end-before: # --- TEST ---
+
+Understanding Component Interfaces
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``wiring.Component`` base class provides a structured way to define interfaces:
+
+- ``In(width)`` and ``Out(width)`` define input and output ports
+
+---------------------------------------------------------------- Type annotations (using Python's standard syntax) define the interface
+----------------------------------------------------------------------- ``super().__init__()`` must be called after defining internal signals
+
+Simulating Your Design
+----------------------
+
+Amaranth has a built-in simulator that allows you to test your designs:
+
+.. literalinclude:: _code/up_counter.py
+ :caption: up_counter_sim.py
+ :linenos:
+ :lines: 46-74
+
+Understanding the Simulation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- **ctx.set(signal, value)**: Sets a signal to a specific value
+
+--------------------------------------------------------------- **ctx.get(signal)**: Gets the current value of a signal
+-------------------------------------------------------- **await ctx.tick()**: Advances simulation by one clock cycle
+------------------------------------------------------------- **sim.add_clock(Period(MHz=1))**: Adds a 1MHz clock to the simulation
+---------------------------------------------------------------------- **sim.write_vcd("file.vcd")**: Generates a waveform file for visualization
+
+Viewing Waveforms
+~~~~~~~~~~~~~~~~~
+
+The VCD file contains all signal changes during simulation. To view it:
+
+1. Install GTKWave: http://gtkwave.sourceforge.net/
+2. Open the VCD file: ``gtkwave counter_sim.vcd``
+3. In GTKWave, select signals in the left panel and add them to the waveform view
+
+Finite State Machines: UART Receiver
+------------------------------------
+
+Now let's implement something more complex - a UART receiver using a Finite State Machine:
+
+.. literalinclude:: _code/uart_receiver.py
+ :caption: uart_receiver.py
+ :linenos:
+
+Understanding FSMs in Amaranth
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- **with m.FSM() as fsm**: Creates a finite state machine
+
+--------------------------------------------------------- **with m.State("NAME")**: Defines a state
+------------------------------------------ **m.next = "STATE"**: Sets the next state
+------------------------------------------ **fsm.ongoing("STATE")**: Checks if the FSM is in a specific state
+------------------------------------------------------------------- **Cat(bit, data)**: Concatenates bits (used for shifting)
+
+Simulating the UART Receiver
+----------------------------
+
+Let's create a simulation to test our UART receiver:
+
+.. literalinclude:: _code/uart_sim.py
+ :caption: uart_sim.py
+ :linenos:
+
+Building a Complete System
+--------------------------
+
+Now let's build a system combining multiple components - a blinker that uses our counter:
+
+.. literalinclude:: _code/controlled_blinker.py
+ :caption: controlled_blinker.py
+ :linenos:
+
+Understanding The System Architecture
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- **Submodules**: ``m.submodules.name = module`` adds a submodule to your design
+
+-------------------------------------------------------------------------------- **Clock Frequency**: Real hardware platforms provide clock frequency info
+-------------------------------------------------------------------------- **Platform Interface**: ``platform.request()`` gets hardware I/O pins
+---------------------------------------------------------------------- **Hierarchical Design**: Components can contain other components
+
+Running on Real Hardware
+------------------------
+
+To run your design on actual FPGA hardware, you need:
+
+1. An FPGA board
+2. The appropriate platform package (e.g., ``amaranth-boards``)
+3. A top-level module that interfaces with the hardware
+
+Here's an example for an iCEStick FPGA board:
+
+.. literalinclude:: _code/program_icestick.py
+ :caption: program_icestick.py
+ :linenos:
+
+For Other Boards
+~~~~~~~~~~~~~~~~
+
+The process is similar for other boards:
+
+1. Import the appropriate platform
+2. Create an instance of your top-level module
+3. Call ``platform.build(module, do_program=True)``
+
+Troubleshooting and Common Errors
+---------------------------------
+
+TypeErrors or AttributeErrors
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block::
+
+ TypeError: Cannot assign to non-Value
+
+- Likely tried to assign to a Python variable instead of a Signal
+
+----------------------------------------------------------------- Always use ``.eq()`` for hardware assignments, not Python ``=``
+
+.. code-block::
+
+ AttributeError: 'Module' object has no attribute 'domain'
+
+- You probably wrote ``m.domain.sync`` instead of ``m.d.sync``
+
+Runtime or Logic Errors
+~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block::
+
+ RuntimeError: Cannot add synchronous assignments: no sync domain is currently active
+
+- You need to define a clock domain
+
+----------------------------------- For simulation, add ``sim.add_clock(Period(MHz=1))``
+
+.. code-block::
+
+ Signal has no timeline
+
+- Signal is not being driven or used in the design
+
+-------------------------------------------------- Check for typos or unused signals
+
+Hardware Deployment Errors
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block::
+
+ OSError: Toolchain binary not found in PATH
+
+- The required synthesis tools (like Yosys) are not installed or not in PATH
+
+---------------------------------------------------------------------------- Install the required tools or add them to PATH
+
+Next Steps
+----------
+
+This tutorial has covered the basics of Amaranth HDL. To continue learning:
+
+1. **Advanced Components**: Explore memory components in ``amaranth.lib.memory``
+2. **Stream Processing**: Learn about streaming interfaces in ``amaranth.lib.stream``
+3. **Clock Domain Crossing**: Study techniques in ``amaranth.lib.cdc``
+4. **Hardware Platforms**: Experiment with FPGA boards using ``amaranth-boards``
+5. **Community Resources**:
+
+ - GitHub: https://github.com/amaranth-lang/amaranth
+ - Documentation: https://amaranth-lang.org
+
+Glossary of Terms
+-----------------
+
+- **HDL**: Hardware Description Language - used to describe electronic circuits
+
+------------------------------------------------------------------------------- **FPGA**: Field-Programmable Gate Array - reconfigurable hardware
+------------------------------------------------------------------ **Combinational Logic**: Logic where outputs depend only on current inputs
+--------------------------------------------------------------------------- **Sequential Logic**: Logic where outputs depend on current inputs and state
+----------------------------------------------------------------------------- **Clock Domain**: Group of logic synchronized to the same clock
+---------------------------------------------------------------- **Elaboration**: Process of transforming Python code into a hardware netlist
+----------------------------------------------------------------------------- **Simulation**: Testing hardware designs in software before physical implementation
+------------------------------------------------------------------------------------ **Synthesis**: Process of transforming a hardware design into physical gates
+----------------------------------------------------------------------------- **VCD**: Value Change Dump - file format for recording signal changes in simulation
+
+External Resources
+------------------
+
+.. note::
+ The following resources from the Amaranth community may also be helpful:
+
+ * `Learning FPGA Design with nMigen `_ by Vivonomicon;
+ * `"I want to learn nMigen" `_ by kbob;
+ * `A tutorial for using Amaranth HDL `_ by Robert Baruch.
+ * `Graded exercises for Amaranth HDL `_ by Robert Baruch.
+ * `My journey with the Amaranth HDL `_ by David Sporn, focussed on setting up the workstation, using formal verification and setting up continuous integration.
\ No newline at end of file