Merge branch 'master' of ssh://git.libre-riscv.org:922/soc
[soc.git] / src / soc / experiment / alu_hier.py
index 76d8e22126d21e06c2da58e774a6befb7504bc34..459bbd951cb41a35e5f06089162e365fd8b03d9b 100644 (file)
@@ -9,7 +9,7 @@ A "real" integer ALU would place the answers onto the output bus after
 only one cycle (sync)
 """
 
-from nmigen import Elaboratable, Signal, Module, Const, Mux, Array
+from nmigen import Elaboratable, Signal, Module, Const, Mux
 from nmigen.hdl.rec import Record, Layout
 from nmigen.cli import main
 from nmigen.cli import verilog, rtlil
@@ -22,12 +22,16 @@ from nmutil.gtkw import write_gtkw
 from nmutil.sim_tmp_alternative import (Simulator, nmigen_sim_top_module,
                                         is_engine_pysim)
 
-from soc.decoder.decode2execute1 import Data
-from soc.decoder.power_enums import MicrOp, Function, CryIn
+from openpower.decoder.decode2execute1 import Data
+from openpower.decoder.power_enums import MicrOp, Function, CryIn
 
 from soc.fu.alu.alu_input_record import CompALUOpSubset
 from soc.fu.cr.cr_input_record import CompCROpSubset
 
+from soc.fu.pipe_data import FUBaseData
+from soc.fu.alu.pipe_data import CommonPipeSpec
+from soc.fu.compunits.compunits import FunctionUnitBaseSingle
+
 import operator
 
 
@@ -105,42 +109,42 @@ class Dummy:
 class DummyALU(Elaboratable):
     def __init__(self, width):
         self.p = Dummy()  # make look like nmutil pipeline API
-        self.p.data_i = Dummy()
-        self.p.data_i.ctx = Dummy()
+        self.p.i_data = Dummy()
+        self.p.i_data.ctx = Dummy()
         self.n = Dummy()  # make look like nmutil pipeline API
-        self.n.data_o = Dummy()
-        self.p.valid_i = Signal()
-        self.p.ready_o = Signal()
-        self.n.ready_i = Signal()
-        self.n.valid_o = Signal()
+        self.n.o_data = Dummy()
+        self.p.i_valid = Signal()
+        self.p.o_ready = Signal()
+        self.n.i_ready = Signal()
+        self.n.o_valid = Signal()
         self.counter = Signal(4)
         self.op = CompCROpSubset()
         i = []
         i.append(Signal(width, name="i1"))
         i.append(Signal(width, name="i2"))
         i.append(Signal(width, name="i3"))
-        self.i = Array(i)
+        self.i = i
         self.a, self.b, self.c = i[0], i[1], i[2]
-        self.out = Array([Signal(width, name="alu_o")])
+        self.out = tuple([Signal(width, name="alu_o")])
         self.o = self.out[0]
         self.width = width
         # more "look like nmutil pipeline API"
-        self.p.data_i.ctx.op = self.op
-        self.p.data_i.a = self.a
-        self.p.data_i.b = self.b
-        self.p.data_i.c = self.c
-        self.n.data_o.o = self.o
+        self.p.i_data.ctx.op = self.op
+        self.p.i_data.a = self.a
+        self.p.i_data.b = self.b
+        self.p.i_data.c = self.c
+        self.n.o_data.o = self.o
 
     def elaborate(self, platform):
         m = Module()
 
         go_now = Signal(reset_less=True)  # testing no-delay ALU
 
-        with m.If(self.p.valid_i):
+        with m.If(self.p.i_valid):
             # input is valid. next check, if we already said "ready" or not
-            with m.If(~self.p.ready_o):
+            with m.If(~self.p.o_ready):
                 # we didn't say "ready" yet, so say so and initialise
-                m.d.sync += self.p.ready_o.eq(1)
+                m.d.sync += self.p.o_ready.eq(1)
 
                 m.d.sync += self.o.eq(self.a)
                 m.d.comb += go_now.eq(1)
@@ -149,14 +153,14 @@ class DummyALU(Elaboratable):
         with m.Else():
             # input says no longer valid, so drop ready as well.
             # a "proper" ALU would have had to sync in the opcode and a/b ops
-            m.d.sync += self.p.ready_o.eq(0)
+            m.d.sync += self.p.o_ready.eq(0)
 
         # ok so the counter's running: when it gets to 1, fire the output
         with m.If((self.counter == 1) | go_now):
             # set the output as valid if the recipient is ready for it
-            m.d.sync += self.n.valid_o.eq(1)
-        with m.If(self.n.ready_i & self.n.valid_o):
-            m.d.sync += self.n.valid_o.eq(0)
+            m.d.sync += self.n.o_valid.eq(1)
+        with m.If(self.n.i_ready & self.n.o_valid):
+            m.d.sync += self.n.o_valid.eq(0)
             # recipient said it was ready: reset back to known-good.
             m.d.sync += self.counter.eq(0)  # reset the counter
             m.d.sync += self.o.eq(0)  # clear the output for tidiness sake
@@ -177,38 +181,86 @@ class DummyALU(Elaboratable):
     def ports(self):
         return list(self)
 
+#####################
+# converting even this dummy ALU over to the FunctionUnit RegSpecs API
+# which, errr, note that the regspecs are totally ignored below, but
+# at least the widths are all 64-bit so it's okay.
+#####################
+
+# input (and output) for logical initial stage (common input)
+
+
+class ALUInputData(FUBaseData):
+    regspec = [('INT', 'a', '0:63'),  # RA
+               ('INT', 'b', '0:63'),  # RB/immediate
+               ]
+
+    def __init__(self, pspec):
+        super().__init__(pspec, False)
+
+
+# output from ALU final stage
+class ALUOutputData(FUBaseData):
+    regspec = [('INT', 'o', '0:63'),        # RT
+               ]
+
+    def __init__(self, pspec):
+        super().__init__(pspec, True)
+
+
+# ALU pipe specification class
+class ALUPipeSpec(CommonPipeSpec):
+    regspec = (ALUInputData.regspec, ALUOutputData.regspec)
+    opsubsetkls = CompALUOpSubset
+
+
+class ALUFunctionUnit(FunctionUnitBaseSingle):
+    # class ALUFunctionUnit(FunctionUnitBaseMulti):
+    fnunit = Function.ALU
+
+    def __init__(self, idx, parent_pspec):
+        super().__init__(ALUPipeSpec, ALU, 1, parent_pspec)
+
 
 class ALU(Elaboratable):
     def __init__(self, width):
+        # XXX major temporary hack: attempting to convert
+        # ALU over to RegSpecs API, FunctionUnitBaseSingle passes in
+        # a regspec here which we can't cope with.  therefore, errr...
+        # just throw it away and set the width to 64
+        if not isinstance(width, int):
+            width = 64
+        # TODO, really this should just inherit from ControlBase it would
+        # be a lot less messy.
         self.p = Dummy()  # make look like nmutil pipeline API
-        self.p.data_i = Dummy()
-        self.p.data_i.ctx = Dummy()
+        self.p.i_data = Dummy()
+        self.p.i_data.ctx = Dummy()
         self.n = Dummy()  # make look like nmutil pipeline API
-        self.n.data_o = Dummy()
-        self.p.valid_i = Signal()
-        self.p.ready_o = Signal()
-        self.n.ready_i = Signal()
-        self.n.valid_o = Signal()
+        self.n.o_data = Dummy()
+        self.p.i_valid = Signal()
+        self.p.o_ready = Signal()
+        self.n.i_ready = Signal()
+        self.n.o_valid = Signal()
         self.counter = Signal(4)
         self.op = CompALUOpSubset(name="op")
         i = []
         i.append(Signal(width, name="i1"))
         i.append(Signal(width, name="i2"))
-        self.i = Array(i)
+        self.i = i
         self.a, self.b = i[0], i[1]
         out = []
         out.append(Data(width, name="alu_o"))
         out.append(Data(width, name="alu_cr"))
-        self.out = Array(out)
+        self.out = tuple(out)
         self.o = self.out[0]
         self.cr = self.out[1]
         self.width = width
-        # more "look like nmutil pipeline API"
-        self.p.data_i.ctx.op = self.op
-        self.p.data_i.a = self.a
-        self.p.data_i.b = self.b
-        self.n.data_o.o = self.o
-        self.n.data_o.cr = self.cr
+        # more "look like nmutil ControlBase pipeline API" stuff
+        self.p.i_data.ctx.op = self.op
+        self.p.i_data.a = self.a
+        self.p.i_data.b = self.b
+        self.n.o_data.o = self.o
+        self.n.o_data.cr = self.cr
 
     def elaborate(self, platform):
         m = Module()
@@ -254,16 +306,16 @@ class ALU(Elaboratable):
         with m.If(go_now):
             # with a combinatorial, no-delay ALU, just pass through
             # the handshake signals to the other side
-            m.d.comb += self.p.ready_o.eq(self.n.ready_i)
-            m.d.comb += self.n.valid_o.eq(self.p.valid_i)
+            m.d.comb += self.p.o_ready.eq(self.n.i_ready)
+            m.d.comb += self.n.o_valid.eq(self.p.i_valid)
         with m.Else():
             # sequential ALU handshake:
-            # ready_o responds to valid_i, but only if the ALU is idle
-            m.d.comb += self.p.ready_o.eq(alu_idle)
-            # select the internally generated valid_o, above
-            m.d.comb += self.n.valid_o.eq(alu_done)
+            # o_ready responds to i_valid, but only if the ALU is idle
+            m.d.comb += self.p.o_ready.eq(alu_idle)
+            # select the internally generated o_valid, above
+            m.d.comb += self.n.o_valid.eq(alu_done)
 
-        # hold the ALU result until ready_o is asserted
+        # hold the ALU result until o_ready is asserted
         alu_r = Signal(self.width)
 
         # output masks
@@ -275,7 +327,7 @@ class ALU(Elaboratable):
         m.d.comb += self.cr.ok.eq(self.op.rc.rc)
 
         with m.If(alu_idle):
-            with m.If(self.p.valid_i):
+            with m.If(self.p.i_valid):
 
                 # as this is a "fake" pipeline, just grab the output right now
                 with m.If(self.op.insn_type == MicrOp.OP_ADD):
@@ -311,7 +363,7 @@ class ALU(Elaboratable):
                 with m.Else():
                     m.d.comb += go_now.eq(1)
 
-        with m.Elif(~alu_done | self.n.ready_i):
+        with m.Elif(~alu_done | self.n.i_ready):
             # decrement the counter while the ALU is neither idle nor finished
             m.d.sync += self.counter.eq(self.counter - 1)
 
@@ -337,10 +389,10 @@ class ALU(Elaboratable):
         yield self.a
         yield self.b
         yield from self.o.ports()
-        yield self.p.valid_i
-        yield self.p.ready_o
-        yield self.n.valid_o
-        yield self.n.ready_i
+        yield self.p.i_valid
+        yield self.p.o_ready
+        yield self.n.o_valid
+        yield self.n.i_ready
 
     def ports(self):
         return list(self)
@@ -362,22 +414,22 @@ class BranchOp(Elaboratable):
 class BranchALU(Elaboratable):
     def __init__(self, width):
         self.p = Dummy()  # make look like nmutil pipeline API
-        self.p.data_i = Dummy()
-        self.p.data_i.ctx = Dummy()
+        self.p.i_data = Dummy()
+        self.p.i_data.ctx = Dummy()
         self.n = Dummy()  # make look like nmutil pipeline API
-        self.n.data_o = Dummy()
-        self.p.valid_i = Signal()
-        self.p.ready_o = Signal()
-        self.n.ready_i = Signal()
-        self.n.valid_o = Signal()
+        self.n.o_data = Dummy()
+        self.p.i_valid = Signal()
+        self.p.o_ready = Signal()
+        self.n.i_ready = Signal()
+        self.n.o_valid = Signal()
         self.counter = Signal(4)
         self.op = Signal(2)
         i = []
         i.append(Signal(width, name="i1"))
         i.append(Signal(width, name="i2"))
-        self.i = Array(i)
+        self.i = i
         self.a, self.b = i[0], i[1]
-        self.out = Array([Signal(width)])
+        self.out = tuple([Signal(width)])
         self.o = self.out[0]
         self.width = width
 
@@ -399,11 +451,11 @@ class BranchALU(Elaboratable):
             ]
 
         go_now = Signal(reset_less=True)  # testing no-delay ALU
-        with m.If(self.p.valid_i):
+        with m.If(self.p.i_valid):
             # input is valid. next check, if we already said "ready" or not
-            with m.If(~self.p.ready_o):
+            with m.If(~self.p.o_ready):
                 # we didn't say "ready" yet, so say so and initialise
-                m.d.sync += self.p.ready_o.eq(1)
+                m.d.sync += self.p.o_ready.eq(1)
 
                 # as this is a "fake" pipeline, just grab the output right now
                 with m.Switch(self.op):
@@ -416,14 +468,14 @@ class BranchALU(Elaboratable):
         with m.Else():
             # input says no longer valid, so drop ready as well.
             # a "proper" ALU would have had to sync in the opcode and a/b ops
-            m.d.sync += self.p.ready_o.eq(0)
+            m.d.sync += self.p.o_ready.eq(0)
 
         # ok so the counter's running: when it gets to 1, fire the output
         with m.If((self.counter == 1) | go_now):
             # set the output as valid if the recipient is ready for it
-            m.d.sync += self.n.valid_o.eq(1)
-        with m.If(self.n.ready_i & self.n.valid_o):
-            m.d.sync += self.n.valid_o.eq(0)
+            m.d.sync += self.n.o_valid.eq(1)
+        with m.If(self.n.i_ready & self.n.o_valid):
+            m.d.sync += self.n.o_valid.eq(0)
             # recipient said it was ready: reset back to known-good.
             m.d.sync += self.counter.eq(0)  # reset the counter
             m.d.sync += self.o.eq(0)  # clear the output for tidiness sake
@@ -449,28 +501,28 @@ def run_op(dut, a, b, op, inv_a=0):
     yield dut.b.eq(b)
     yield dut.op.insn_type.eq(op)
     yield dut.op.invert_in.eq(inv_a)
-    yield dut.n.ready_i.eq(0)
-    yield dut.p.valid_i.eq(1)
-    yield dut.n.ready_i.eq(1)
+    yield dut.n.i_ready.eq(0)
+    yield dut.p.i_valid.eq(1)
+    yield dut.n.i_ready.eq(1)
     yield
 
     # wait for the ALU to accept our input data
-    while not (yield dut.p.ready_o):
+    while not (yield dut.p.o_ready):
         yield
 
-    yield dut.p.valid_i.eq(0)
+    yield dut.p.i_valid.eq(0)
     yield dut.a.eq(0)
     yield dut.b.eq(0)
     yield dut.op.insn_type.eq(0)
     yield dut.op.invert_in.eq(0)
 
     # wait for the ALU to present the output data
-    while not (yield dut.n.valid_o):
+    while not (yield dut.n.o_valid):
         yield
 
     # latch the result and lower read_i
     result = yield dut.o.data
-    yield dut.n.ready_i.eq(0)
+    yield dut.n.i_ready.eq(0)
 
     return result
 
@@ -520,21 +572,21 @@ def test_alu_parallel():
     sim.add_clock(1e-6)
 
     def send(a, b, op, inv_a=0, rc=0):
-        # present input data and assert valid_i
+        # present input data and assert i_valid
         yield dut.a.eq(a)
         yield dut.b.eq(b)
         yield dut.op.insn_type.eq(op)
         yield dut.op.invert_in.eq(inv_a)
         yield dut.op.rc.rc.eq(rc)
-        yield dut.p.valid_i.eq(1)
+        yield dut.p.i_valid.eq(1)
         yield
-        # wait for ready_o to be asserted
-        while not (yield dut.p.ready_o):
+        # wait for o_ready to be asserted
+        while not (yield dut.p.o_ready):
             yield
-        # clear input data and negate valid_i
+        # clear input data and negate i_valid
         # if send is called again immediately afterwards, there will be no
         # visible transition (they will not be negated, after all)
-        yield dut.p.valid_i.eq(0)
+        yield dut.p.i_valid.eq(0)
         yield dut.a.eq(0)
         yield dut.b.eq(0)
         yield dut.op.insn_type.eq(0)
@@ -543,18 +595,18 @@ def test_alu_parallel():
 
     def receive():
         # signal readiness to receive data
-        yield dut.n.ready_i.eq(1)
+        yield dut.n.i_ready.eq(1)
         yield
-        # wait for valid_o to be asserted
-        while not (yield dut.n.valid_o):
+        # wait for o_valid to be asserted
+        while not (yield dut.n.o_valid):
             yield
         # read results
         result = yield dut.o.data
         cr = yield dut.cr.data
-        # negate ready_i
+        # negate i_ready
         # if receive is called again immediately afterwards, there will be no
         # visible transition (it will not be negated, after all)
-        yield dut.n.ready_i.eq(0)
+        yield dut.n.i_ready.eq(0)
         return result, cr
 
     def producer():
@@ -585,6 +637,8 @@ def test_alu_parallel():
         yield from send(0x80, 2, MicrOp.OP_EXTS, rc=1)
         # sign extend -128 (8 bits)
         yield from send(2, 0x80, MicrOp.OP_EXTSWSLI)
+        # 5 - 5
+        yield from send(5, 5, MicrOp.OP_CMP, rc=1)
 
     def consumer():
         # receive and check results, interspersed with wait states
@@ -595,11 +649,13 @@ def test_alu_parallel():
         result = yield from receive()
         assert result[0] == 8
         # 2 * 3 = 6
+        # 6 > 0 => CR = 0b100
         result = yield from receive()
         assert result == (6, 0b100)
         yield
         yield
         # (-6) + 3 = -3
+        # -3 < 0 => CR = 0b010
         result = yield from receive()
         assert result == (65533, 0b010)  # unsigned equivalent to -2
         # 5 - 3 = 2
@@ -619,11 +675,16 @@ def test_alu_parallel():
         result = yield from receive()
         assert result[0] == 13
         # sign extend -128 (8 bits) = -128 (16 bits)
+        # -128 < 0 => CR = 0b010
         result = yield from receive()
         assert result == (0xFF80, 0b010)
         # sign extend -128 (8 bits) = -128 (16 bits)
         result = yield from receive()
         assert result[0] == 0xFF80
+        # 5 - 5 = 0
+        # 0 == 0 => CR = 0b001
+        result = yield from receive()
+        assert result == (0, 0b001)
 
     sim.add_sync_process(producer)
     sim.add_sync_process(consumer)
@@ -641,13 +702,13 @@ def write_alu_gtkw(gtkw_name, clk_period=1e-6, sub_module=None,
         'i2[15:0]',
         'op__insn_type' if pysim else 'op__insn_type[6:0]',
         'op__invert_in',
-        'valid_i',
-        'ready_o',
-        'valid_o',
-        'ready_i',
+        'i_valid',
+        'o_ready',
+        'o_valid',
+        'i_ready',
         'alu_o[15:0]',
         'alu_o_ok',
-        'alu_cr[2:0]',
+        'alu_cr[15:0]',
         'alu_cr_ok'
     ]
     # determine the module name of the DUT