Skip to main content

Soft-core arithmetic components written in Amaranth HDL

Project description

smolarith

Documentation Status main: CI next: CI

Small arithmetic soft-cores for smol FPGAs. If your FPGA has hard IP implementing functions in this repository, you should use those instead.

Example

from amaranth import signed, Module, C
from amaranth.lib.wiring import Component, Out, In
from amaranth.lib.stream import Signature
from amaranth.back.verilog import convert
from amaranth.sim import Simulator

import sys
from smolarith import mul
from smolarith.mul import MulticycleMul


class Celsius2Fahrenheit(Component):
    """Module to convert Celsius temperatures to Fahrenheit (F = 1.8*C + 32)."""

    def __init__(self, *, qc, qf, scale_const=5):
        self.qc = qc
        self.qf = qf
        self.scale_const = scale_const

        self.c_width = self.qc[0] + self.qc[1]
        self.f_width = self.qf[0] + self.qf[1]
        # 1.8 not representable. 1.78125 will have to be close enough.
        # Q1.{self.scale_const}
        self.mul_factor = C(9*2**self.scale_const // 5)
        # Q6.{self.qc[1] + self.scale_const}
        self.add_factor = C(32 << (self.qc[1] + self.scale_const))
        # Mul result will have self.qc[1] + self.scale_const fractional bits.
        # Adjust to desired Fahrenheit precision.
        self.extra_bits = self.qc[1] + self.scale_const - self.qf[1]

        # Output will be 2*max(len(self.mul_factor), self.c_width)...
        # more bits than we need.
        self.mul = MulticycleMul(width=max(len(self.mul_factor),
                                           self.c_width))

        super().__init__({
            "c": In(Signature(signed(self.c_width))),
            "f": Out(Signature(signed(self.f_width))),
        })

    def elaborate(self, plat):
        m = Module()
        m.submodules.mul = self.mul

        m.d.comb += [
            # res = 1.8*C
            self.c.ready.eq(self.mul.inp.ready),
            self.mul.inp.valid.eq(self.c.valid),
            self.mul.inp.payload.a.eq(self.c.payload),
            self.mul.inp.payload.b.eq(self.mul_factor),
            self.mul.inp.payload.sign.eq(mul.Sign.SIGNED_UNSIGNED),

            # F = res + 32, scaled to remove frac bits we don't need.
            self.f.payload.eq((self.mul.outp.payload.o + self.add_factor) >>
                              self.extra_bits),
            self.f.valid.eq(self.mul.outp.valid),
            self.mul.outp.ready.eq(self.f.ready)
        ]

        return m


def sim(*, c2f, start_c, end_c, gtkw=False):
    sim = Simulator(c2f)
    sim.add_clock(1e-6)

    async def tb(ctx):
        await ctx.tick()

        ctx.set(c2f.f.ready, 1)
        await ctx.tick()

        for i in range(start_c, end_c):
            ctx.set(c2f.c.payload, i)
            ctx.set(c2f.c.valid, 1)
            await ctx.tick()
            ctx.set(c2f.c.valid, 0)

            # Wait for module to calculate results.
            await ctx.tick().until(c2f.f.valid == 1)

            # This is a low-effort attempt to print fixed-point numbers
            # by converting them into floating point.
            print(ctx.get(c2f.c.payload) / 2**c2f.qc[1],
                  ctx.get(c2f.f.payload) / 2**c2f.qf[1])

    sim.add_testbench(tb)

    if gtkw:
        with sim.write_vcd("c2f.vcd", "c2f.gtkw"):
            sim.run()
    else:
        sim.run()


if __name__ == "__main__":
    # See: https://en.wikipedia.org/wiki/Q_(number_format)
    c2f = Celsius2Fahrenheit(qc=(8, 3), qf=(10, 3), scale_const=15)

    if len(sys.argv) > 1 and sys.argv[1] == "sim":
        if len(sys.argv) >= 2:
            start_c = int(float(sys.argv[2]) * 2**c2f.qc[1])
        else:
            start_c = -2**(c2f.qc[0] + c2f.qc[1] - 1)
        
        if len(sys.argv) >= 3:
            end_c = int(float(sys.argv[3]) * 2**c2f.qc[1])
        else:
            end_c = 2**(c2f.qc[0] + c2f.qc[1] - 1)

        sim(c2f=c2f, start_c=start_c, end_c=end_c, gtkw=False)
    else:
        print(convert(c2f))

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

smolarith-0.2.0.tar.gz (21.7 kB view details)

Uploaded Source

Built Distribution

smolarith-0.2.0-py3-none-any.whl (18.2 kB view details)

Uploaded Python 3

File details

Details for the file smolarith-0.2.0.tar.gz.

File metadata

  • Download URL: smolarith-0.2.0.tar.gz
  • Upload date:
  • Size: 21.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.0 CPython/3.12.4

File hashes

Hashes for smolarith-0.2.0.tar.gz
Algorithm Hash digest
SHA256 ef7c8ec34e91ce12a6451c9e059227dd9ca653cbe20c32c379260ca15cc078c4
MD5 b5e17bfa59fc987023d61059f9b865de
BLAKE2b-256 3cc33110b0b9ab8af9553336bb8257fb6b1c8a24f6107c28ae144f529441b005

See more details on using hashes here.

File details

Details for the file smolarith-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: smolarith-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 18.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.0 CPython/3.12.4

File hashes

Hashes for smolarith-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2d86e11ce5479dea460ef73e2023acce6e16ca7eb0137778be5b3bb032f315ba
MD5 bb134f844723a65da5e2c44500a2a15d
BLAKE2b-256 f47650dfdf7d78f1bb85181fcd40f1ff4f81a060c7bc8bee01c864164659d53f

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page