User Tools

Site Tools


nmigen:nmigen_sim_testbench_sync

Creating a multiplier

Here we do a multiplication of 'a' * 'b' using a shifter with n step. n being the amount of bits. example a = 1101, b = 0101. (a=13 , b=5) we shift 'a' to the left by 1 bit at each step, and if the coresponding bit of 'b' is 1, we add 'a' shifted to the result:

 b[0] * (a << 0)
+
 b[1] * (a << 1)
+
 b[2] * (a << 2)
+
 b[3] * (a << 3)
--------------
o = 00001101 + 00110100 = 13+52 = 65

So let's implement our multiplier

class Multiplier(Elaboratable):
    def __init__(self, width):
 
        # o = a * b
        self.a = Signal(width)
        self.b = Signal(width)
 
        # result has 2x more bits
        self.o = Signal(2*width)
 
        # used to start our multiplication
        self.start = Signal()
 
        # notify us when done
        self.done = Signal()
 
        self.width = width
 
    def elaborate(self, platform):
        m = Module()
 
        #tmp holds 'a' shifted at each step of 1 bit
        tmp = Signal(2*self.width)
 
        # counter will count the amount of addition we are doing
        cnt = Signal.range(0, self.width)
 
        #we are done when the cnt has reached 'width-1' steps
        m.d.comb += self.done.eq(cnt == self.width-1)
 
        #check if we got 'start' asserted, if so reset the output and counter
        with m.If(self.start):
            m.d.sync += [
                tmp.eq(self.a),
                cnt.eq(0),
                self.o.eq(0)
            ]
 
        with m.Elif(~self.done):
            #as long as we are not done, let's shift tmp (which is a copy of 'a') and increment counter 
            m.d.sync += [
                tmp.eq(tmp << 1),
                cnt.eq(cnt + 1)
            ]
 
            #if the bit at position 'cnt' of 'b' is 1, then add 'a' shifted 'cnt' times (which is tmp)
            with m.If(self.b.bit_select(cnt, 1)):
                m.d.sync += self.o.eq(self.o + tmp)
 
 
        return m

We then need a testbench that will take 2 values, tick once with 'start'=1 then a couple of times with 'start'=0 otherwise the core will remain in the start position

if __name__ == "__main__":
    dut = Multiplier(4)
    with Simulator(dut, vcd_file=open("multiplier.vcd", "w")) as sim:
        def process():
            # set a, b, and start
            yield dut.a.eq(13)
            yield dut.b.eq(5)
            yield dut.start.eq(1)
            # one clock tick here
            yield Tick()
            # deassert just 'start'
            yield dut.start.eq(0)
            # 10 tick here
            for i in range(10):
                yield Tick()
 
        # Add a clock to our design
        sim.add_clock(1e-6)
        # Add 'process' as a testbench process
        sim.add_sync_process(process)
        # Run the simulation
        sim.run()

Here is the entire code

mult.py
from nmigen import *
from nmigen.cli import main
from nmigen.back.pysim import *
from nmigen.test.tools import *
 
 
class Multiplier(Elaboratable):
    def __init__(self, width):
        self.a = Signal(width)
        self.b = Signal(width)
        self.o = Signal(2*width)
        self.start = Signal()
        self.done = Signal()
 
        self.width = width
 
    def elaborate(self, platform):
        m = Module()
 
        tmp = Signal(2*self.width)
        cnt = Signal.range(0, self.width)
 
        m.d.comb += self.done.eq(cnt == self.width-1)
 
        with m.If(self.start):
            m.d.sync += [
                tmp.eq(self.a),
                cnt.eq(0),
                self.o.eq(0)
            ]
 
        with m.Elif(~self.done):
            m.d.sync += tmp.eq(tmp << 1)
            m.d.sync += cnt.eq(cnt + 1)
            with m.If(self.b.bit_select(cnt, 1)):
                m.d.sync += self.o.eq(self.o + tmp)
 
        return m
 
 
if __name__ == "__main__":
    dut = Multiplier(4)
    with Simulator(dut, vcd_file=open("multiplier.vcd", "w")) as sim:
        def process():
            yield dut.a.eq(13)
            yield dut.b.eq(5)
            yield dut.start.eq(1)
            yield Tick()
            yield dut.start.eq(0)
            for i in range(10):
                yield Tick()
 
        sim.add_clock(1e-6)
        sim.add_sync_process(process)
        sim.run()

This testbench has generated a vcd file we can look at with gtkwave As we can see, after a couple of clock tick, we have 'o' that is 65, which is our multiplication value

nmigen/nmigen_sim_testbench_sync.txt · Last modified: 2019/10/09 18:29 by ramtin