Skip to content

Commit

Permalink
gateware.accumulator: new module.
Browse files Browse the repository at this point in the history
Wide counters (24-bit and wider) are problematic on the fairly slow
iCE40 architecture, and this module allows using counters of any width
by pipelining them.

Co-authored-by: Wanda <wanda@phinode.net>
  • Loading branch information
whitequark and wanda-phi committed Apr 20, 2024
1 parent 59c26de commit a15df39
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 0 deletions.
64 changes: 64 additions & 0 deletions software/glasgow/gateware/accumulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import operator

from amaranth import *
from amaranth.lib import wiring
from amaranth.lib.wiring import In, Out


class Accumulator(wiring.Component):
"""Pipelined arithmetic accumulator.
Computes :py:`new_sum = old_sum + addend` using at most :py:`stage_width` wide adders, with
a latency of :py:`(width + stage_width - 1) // stage_width` cycles and throughput of one
addition per cycle.
Members
-------
addend : In(width)
Addend.
sum : Out(width)
Accumulated sum.
"""
def __init__(self, width, *, stage_width=16):
self._width = operator.index(width)
self._stage_width = operator.index(stage_width)
assert self._width >= 1 and self._stage_width >= 1
self._stages = 1 + (self._width + self._stage_width - 1) // self._stage_width
super().__init__({
"addend": In(self._width),
"sum": Out(self._width)
})

@property
def stages(self):
return self._stages

def elaborate(self, platform):
m = Module()

carry = Const(0)
addend = Signal.like(self.addend)
result = Cat()

m.d.sync += addend.eq(self.addend)

for index, start_at in enumerate(range(0, self._width, self._stage_width)):
stage_width = min(self._width - start_at, self._stage_width)

carry_next = Signal(name=f"carry{index}")
addend_next = Signal.like(addend[stage_width:], name=f"addend{index}")
result_next = Signal.like(result, name=f"result{index}")
stage = Signal(stage_width, name=f"stage{index}")

m.d.sync += Cat(stage, carry_next).eq(stage + addend[:stage_width] + carry)
m.d.sync += addend_next.eq(addend[stage_width:])
m.d.sync += result_next.eq(result)

carry = carry_next
addend = addend_next
result = Cat(result_next, stage)

m.d.comb += self.sum.eq(result)

return m

25 changes: 25 additions & 0 deletions software/tests/gateware/test_accumulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import unittest
from amaranth import *
from amaranth.sim import Tick

from glasgow.gateware import simulation_test
from glasgow.gateware.accumulator import Accumulator


class AccumulatorTestCase(unittest.TestCase):
def setUp(self):
self.tb = Accumulator(5, stage_width=2)

@simulation_test()
def test_counter(self, tb):
total = 0
queue = [0] * (self.tb.stages + 1)
for i in range(100):
addend = i * 2137 % 32
total += addend
total %= 32
queue.append(total)
self.assertEqual(queue[0], (yield self.tb.sum))
del queue[0]
yield self.tb.addend.eq(addend)
yield Tick()

0 comments on commit a15df39

Please sign in to comment.