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
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