# Getting started

This section demonstrates the basic Amaranth workflow to provide a cursory overview of the language and the toolchain. See the tutorial for a step-by-step introduction to the language, and the language guide for a detailed explanation of every language construct.

## A counter

As a first example, consider a counter with a fixed limit, enable, and overflow. The code for this example is shown below. `Download` and run it:

    $ python3 up_counter.py

### Implementing a counter

A 16-bit up counter with enable input, overflow output, and a limit fixed at design time can be implemented in Amaranth as follows:

     1from amaranth import *
     2from amaranth.lib import wiring
     3from amaranth.lib.wiring import In, Out
     4
     5
     6class UpCounter(wiring.Component):
     7    """
     8    A 16-bit up counter with a fixed limit.
     9
    10    Parameters
    11    ----------
    12    limit : int
    13        The value at which the counter overflows.
    14
    15    Attributes
    16    ----------
    17    en : Signal, in
    18        The counter is incremented if ``en`` is asserted, and retains
    19        its value otherwise.
    20    ovf : Signal, out
    21        ``ovf`` is asserted when the counter reaches its limit.
    22    """
    23
    24    en: In(1)
    25    ovf: Out(1)
    26
    27    def __init__(self, limit):
    28        self.limit = limit
    29        self.count = Signal(16)
    30
    31        super().__init__()
    32
    33    def elaborate(self, platform):
    34        m = Module()
    35
    36        m.d.comb += self.ovf.eq(self.count == self.limit)
    37
    38        with m.If(self.en):
    39            with m.If(self.ovf):
    40                m.d.sync += self.count.eq(0)
    41            with m.Else():
    42                m.d.sync += self.count.eq(self.count + 1)
    43
    44        return m

The reusable building block of Amaranth designs is a `Component`: a Python class declares its interface (`en` and `ovf`, in this case) and implements the `elaborate` method that defines its behavior.

Most `elaborate` implementations use a `Module` helper to describe combinational (`m.d.comb`) and synchronous (`m.d.sync`) logic controlled with conditional syntax (`m.If`, `m.Elif`, `m.Else`) similar to Python’s. They can also instantiate vendor-defined black boxes or modules written in other HDLs.

### Testing a counter

To verify its functionality, the counter can be simulated for a small amount of time, with a test bench driving it and checking a few simple conditions:

    46from amaranth.sim import Simulator, Period
    47
    48
    49dut = UpCounter(25)
    50async def bench(ctx):
    51    # Disabled counter should not overflow.
    52    ctx.set(dut.en, 0)
    53    for _ in range(30):
    54        await ctx.tick()
    55        assert not ctx.get(dut.ovf)
    56
    57    # Once enabled, the counter should overflow in 25 cycles.
    58    ctx.set(dut.en, 1)
    59    for _ in range(24):
    60        await ctx.tick()
    61        assert not ctx.get(dut.ovf)
    62    await ctx.tick()
    63    assert ctx.get(dut.ovf)
    64
    65    # The overflow should clear in one cycle.
    66    await ctx.tick()
    67    assert not ctx.get(dut.ovf)
    68
    69
    70sim = Simulator(dut)
    71sim.add_clock(Period(MHz=1))
    72sim.add_testbench(bench)
    73with sim.write_vcd("up_counter.vcd"):
    74    sim.run()

The testbench is implemented as a Python `async` function that is simulated concurrently with the counter itself. The testbench can inspect the simulated signals using `ctx.get(sig)`, update them using `ctx.set(sig, val)`, and advance the simulation by one clock cycle with `await ctx.tick()`. See the simulator documentation for details.

When run, the testbench finishes successfully, since all of the assertions hold, and produces a VCD file with waveforms recorded for every `Signal` as well as the clock of the `sync` domain:

### Converting a counter

Although some Amaranth workflows do not include Verilog at all, it is still the de facto standard for HDL interoperability. Any Amaranth design can be converted to synthesizable Verilog using the corresponding backend:

    76from amaranth.back import verilog
    77
    78
    79top = UpCounter(25)
    80with open("up_counter.v", "w") as f:
    81    f.write(verilog.convert(top))

The signals that will be connected to the ports of the top-level Verilog module should be specified explicitly. The rising edge clock and synchronous reset signals of the `sync` domain are added automatically; if necessary, the control signals can be configured explicitly. The result is the following Verilog code (lightly edited for clarity):

     1(* generator = "Amaranth" *)
     2module top(ovf, clk, rst, en);
     3  reg \$auto$verilog_backend.cc:2255:dump_module$1  = 0;
     4  (* src = "up_counter.py:36" *)
     5  wire \$1 ;
     6  (* src = "up_counter.py:42" *)
     7  wire [16:0] \$3 ;
     8  (* src = "up_counter.py:42" *)
     9  wire [16:0] \$4 ;
    10  (* src = "<site-packages>/amaranth/hdl/ir.py:509" *)
    11  input clk;
    12  wire clk;
    13  (* src = "up_counter.py:29" *)
    14  reg [15:0] count = 16'h0000;
    15  (* src = "up_counter.py:29" *)
    16  reg [15:0] \count$next ;
    17  (* src = "<site-packages>/amaranth/lib/wiring.py:1647" *)
    18  input en;
    19  wire en;
    20  (* src = "<site-packages>/amaranth/lib/wiring.py:1647" *)
    21  output ovf;
    22  wire ovf;
    23  (* src = "<site-packages>/amaranth/hdl/ir.py:509" *)
    24  input rst;
    25  wire rst;
    26  assign \$1  = count == (* src = "up_counter.py:36" *) 5'h19;
    27  assign \$4  = count + (* src = "up_counter.py:42" *) 1'h1;
    28  always @(posedge clk)
    29    count <= \count$next ;
    30  always @* begin
    31    if (\$auto$verilog_backend.cc:2255:dump_module$1 ) begin end
    32    \count$next  = count;
    33    (* src = "up_counter.py:38" *)
    34    if (en) begin
    35      (* full_case = 32'd1 *)
    36      (* src = "up_counter.py:39" *)
    37      if (ovf) begin
    38        \count$next  = 16'h0000;
    39      end else begin
    40        \count$next  = \$4 [15:0];
    41      end
    42    end
    43    (* src = "<site-packages>/amaranth/hdl/xfrm.py:534" *)
    44    if (rst) begin
    45      \count$next  = 16'h0000;
    46    end
    47  end
    48  assign \$3  = \$4 ;
    49  assign ovf = \$1 ;
    50endmodule

To aid debugging, the generated Verilog code has the same general structure as the Amaranth source code (although more verbose), and contains extensive source location information.

Note

Unfortunately, at the moment none of the supported toolchains will use the source location information in diagnostic messages.

## A blinking LED

Although Amaranth works well as a standalone HDL, it also includes a build system that integrates with FPGA toolchains, and many board definition files for common developer boards that include pinouts and programming adapter invocations. The following code will blink a LED with a frequency of 1 Hz on any board that has a LED and an oscillator:

     1from amaranth import *
     2
     3
     4class LEDBlinker(Elaboratable):
     5    def elaborate(self, platform):
     6        m = Module()
     7
     8        led = platform.request("led")
     9
    10        half_freq = int(platform.default_clk_period.hertz // 2)
    11        timer = Signal(range(half_freq + 1))
    12
    13        with m.If(timer == half_freq):
    14            m.d.sync += led.o.eq(~led.o)
    15            m.d.sync += timer.eq(0)
    16        with m.Else():
    17            m.d.sync += timer.eq(timer + 1)
    18
    19        return m

The `LEDBlinker` module will use the first LED available on the board, and derive the clock divisor from the oscillator frequency specified in the clock constraint. It can be used, for example, with the Lattice iCEStick evaluation board, one of the many boards already supported by Amaranth:

Todo

Link to the installation instructions for the FOSS iCE40 toolchain, probably as a part of board documentation.

    21from amaranth_boards.icestick import ICEStickPlatform
    22
    23
    24ICEStickPlatform().build(LEDBlinker(), do_program=True)

With only a single line of code, the design is synthesized, placed, routed, and programmed to the on-board Flash memory. Although not all applications will use the Amaranth build system, the designs that choose it can benefit from the “turnkey” built-in workflows; if necessary, the built-in workflows can be customized to include user-specified options, commands, and files.

Note

The ability to check with minimal effort whether the entire toolchain functions correctly is so important that it is built into every board definition file. To use it with the iCEStick board, run:

    $ python3 -m amaranth_boards.icestick

This command will build and program a test bitstream similar to the example above.