# Amaranth > [[FPGA]], [[Open source MOC|open source]], [[ICE40 Overview]] See: https://amaranth-lang.org/docs/amaranth/latest/ Tutorial: https://github.com/robertbaruch/amaranth-tutorial Learning exercises: https://github.com/robertbaruch/amaranth-exercises [[amaranth Finite State Machines]] [[Simulating in Amaranth]] ## Background Amaranth consists of: - Language - Standard Library - Simulator - Buildsystem ### Amaranth Lanuguage - Python library - Python code is used to construct a netlist of a digital circuit design - Amaranth is made to work with other python tools - Amaranth tries to be hard to miss-use - Usability issues are considered reportable bugs ### Amaranth standard library - Collection of essentila digital design componets and interfaces such as [[FIFO First In First Out Buffer]] - Using an amaranth component helps minimized the amount of vendor specific code that you need to write. ### Simulator - You can use python to write simulator test benches for your design to interface with Running an amarath `.py` file -> generate a verilog '.v'' file and a simulation `.vcd` file which you can view in [[GTKWave]] ## Installation ### Mac 1. Install [[Homebrew]] if not already installed 2. Install Amaranth - I am installing the development snapshot here, last release in main was a year ago - `pip install --user --upgrade 'amaranth[builtin-yosys]'` 3. Install board definitions? - no instructions for this yet apparently ## Language guide - Values - A binary number computed or stored anywhere in the design - Has width - number of bits used to represent value - Has [[signedness]] - With and signedness together are called shape - Constant - Simplest value - Called as either - Const(...) - C(...) - Shape - Shape is an object with 2 attributes: .width and .signed - Shapes of values - All values have a shape - .shape() can compute a values shape - Example - The width of a value v can be retrived with either: - v.shape().width - len(v) - Shape casting - Shapes can be *cast* from other objects - Called shape-castable - So if you have a list of data, and you want to make sure your shape can contain all of the potential values, you could cast the shape to that list. - So if you have some class defining a state machine, casting would let you reliably have the shape for the output value of the state machine fully contain the output value - This seems to be inline with Amaranth being a language focusing on usability. - Value casting - Values may be cast from other objects - Casting can be done explicitly with value.cast - BUT casting is also implicit, since objects that can be cast as values are accepted in the same places that values are - Signals - a signal is a value representing a potentially varying number - Signals can be assigned in [[combinatorial domain]] or [[synchronous domain]] - generated as [[wires - FPGA]] and [[registers - FPGA]] respectively - This makes sense as the combinatorial domain is where functions of the design that remain fixed get defined, and synchronous domain is where memory like functionality can be created - This video from the [[FPGAs for Beginners Youtube Channel]] covers the theory of these different domains: https://www.youtube.com/watch?v=oROOR6ylVF4 - signals can not be undefined of uniniotialized - They are going to be implemented in the FPGA as actual hardware, so yes you do have to fully define hardware - signal can be created with an explicitly defined shape - Signal Name - each signal has a name, which gets used in: - waveform viewer - diagnostic messages - Verilog output - etc. - Most cases signal name is inferred from the name of the variable or attribute the signal is placed into - Can be explicitly defined with the name= paramater - Initial signal value - Each signal and an initial value - Specified with the reset= parameter - If no initial value is specified, defaults to 0 - Signals assigned in a [[combinatorial domain]] assume their initial value when none of the assignments are active - Signals assigned in the [[synchronous domain]] assume initial value after power on reset and under explicit reset - Signals used but never assigned are equivalent to the constants of the initial value - Reset-less signals can be defined ## Operators - Values can be combined with each other or other value-castable objects with: - arithmetic operations - bitwise operations - logical operations - bit sequence operations - other operators - the resulting form is an experession - *which is also a value* - Performing or describing computations? - Code written in python computes concrete objects with the goal of outputting a concrete result - Amaranth code ***Describes*** computations on many potential abstract objects - Goal is to generate the described circuit which can then do the operation when simulated or deployed on the FPGA - amaranth expressions don't compute the results in python - they instead are python objects that describe a circuit you could synthesis that would carry out the operation - This means that amaranth values exist on a higher level of abstraction than the python that describes it - So if you want to create an if statement that runs on the FPGA, you cant just use python if - You have to use python to describe the resulting circuit of an if statement - Amaranth has control structures for this - Width Extension - Many operations will automatically zero extend or sign extend values as necessary to complete operation - So if you use an add operator, and the result could have a wider shape than the original values, then the widh will be increased - Arithmetic operators ### Assigning the value of a signal ```python s = Signal() s.eq(1) ``` ### Multiplexing signals ```python y.eq(Mux(cond,x1,x2)) ``` If `cond` is true, then `y` is `x1`, otherwise, it is `x2` ## Modules `comb` = [[combinatorial domain]] - static lookup tables `sync` = [[synchronous domain]] - updates with clock cycle Adding assignments to a domain: ```python a = Signal() b = Signal() c = Signal() m.d.comb += a.eq(1) m.d.sync += [ b.eq(c), c.eq(b), ] ``` ## Interfacing with pads https://github.com/RobertBaruch/amaranth-tutorial/blob/main/9_synthesis.md ### Direction types: - `i`: input only. Signal for the pin is `.i` - `o`: output only. Signal for the pin is `.o` - `io`: bidirectional. Signals for the pin are `.i` for the input, `.o` for the output, and `.oe` is the direction for the pin: 0 for input, 1 for output - `oe`: tristate. Signals for the pin are `.o` for the output, and `.oe` to enable output: 0 for disable, 1 for enable ### Resources A resource configures pins on the device, and allows you to request those pins in an `elaborate` function. A `Pin` has `Signal`s associated with it, including `i` and `o` (input/output) A resource includes: name, number, and items to configure Ex: `Resource("abc",0,Pins("J3",dir="i")` To get the input Signal from "abc": `platform.request("abc").i` ### Resource configuration items - `Pins` - `PinsN` - all pins are active low - `DiffPairs` - ![[Screenshot 2023-05-16 at 5.27.49 PM.jpg]] - `Clock` - a clock with a given frequency in Hz - `Attrs` - "platform-specific attributes such as voltage standard to select" #### Example: ```python Resource("clk", 0, Pins("J3", dir="i"), Clock(12e6), Attrs(GLOBAL=True, IO_STANDARD="SB_LVCMOS")) ``` > This says that the `clk` resource is at pin `J3` on the FPGA, has a frequency of 12MHz, is a "global" signal, and uses the LVCMOS voltage standard. Without knowing about the toolchain for the platform, you will not know what attributes are required. ```python Resource("sata", 0, Subsignal("tx", DiffPairs("AD16", "AD17", dir="o")), Subsignal("rx", DiffPairs("AF15", "AF16", dir="i")), Attrs(IO_TYPE="==LVDS==") ), ``` #### Known Lattice ICE40 Attributes - `GLOBAL`: bool. If True, the pin is global. Global pins are designated `GBIN` in the datasheet pin listing. - `IO_STANDARD`: string: - `SB_LVCMOS`: for all single-ended pins and differential output pins - `SB_LVDS_INPUT`: for differential input pins ### Connectors Are a different thing ### Reqesting numbered resoruces - There are 5 LEDs on glasgow - If you want to request say the 2nd LED, you would - `LED_2 = platform.request("led",1)` ## Defining a board Properties of a board definition: - `device` - `package` - `resources` - `default_clk` - `default_rst` - `connectors` [[Glasgow board definition in Amaranth]]