1 """ nmigen implementation of buffered pipeline stage, based on zipcpu:
2 https://zipcpu.com/blog/2017/08/14/strategies-for-pipelining.html
4 from nmigen
import Signal
, Cat
, Const
, Mux
, Module
5 from nmigen
.compat
.sim
import run_simulation
6 from nmigen
.cli
import verilog
, rtlil
9 """ buffered pipeline stage
11 stage-1 i_p_stb >>in stage o_n_stb out>> stage+1
12 stage-1 o_p_busy <<out stage i_n_busy <<in stage+1
13 stage-1 i_data >>in stage o_data out>> stage+1
21 #self.i_p_rst = Signal() # >>in - comes in from PREVIOUS stage
22 self
.i_p_stb
= Signal() # >>in - comes in from PREVIOUS stage
23 self
.i_n_busy
= Signal() # in<< - comes in from the NEXT stage
24 self
.i_data
= Signal(32) # >>in - comes in from the PREVIOUS stage
25 #self.i_rst = Signal()
28 self
.r_data
= Signal(32)
31 self
.o_n_stb
= Signal() # out>> - goes out to the NEXT stage
32 self
.o_p_busy
= Signal() # <<out - goes out to the PREVIOUS stage
33 self
.o_data
= Signal(32) # out>> - goes out to the NEXT stage
35 def pre_process(self
, d_in
):
38 def process(self
, d_in
):
41 def elaborate(self
, platform
):
44 o_p_busyn
= Signal(reset_less
=True)
45 o_n_stbn
= Signal(reset_less
=True)
46 i_p_stb_o_p_busyn
= Signal(reset_less
=True)
47 m
.d
.comb
+= o_n_stbn
.eq(~self
.o_n_stb
)
48 m
.d
.comb
+= o_p_busyn
.eq(~self
.o_p_busy
)
49 m
.d
.comb
+= i_p_stb_o_p_busyn
.eq(self
.i_p_stb
& o_p_busyn
)
52 m
.d
.comb
+= result
.eq(self
.process(self
.i_data
))
53 with m
.If(o_p_busyn
): # not stalled
54 m
.d
.sync
+= self
.r_data
.eq(result
)
56 #with m.If(self.i_p_rst): # reset
57 # m.d.sync += self.o_n_stb.eq(0)
58 # m.d.sync += self.o_p_busy.eq(0)
59 with m
.If(~self
.i_n_busy
): # previous stage is not busy
60 with m
.If(o_p_busyn
): # not stalled
61 # nothing in buffer: send input direct to output
62 m
.d
.sync
+= self
.o_n_stb
.eq(self
.i_p_stb
)
63 m
.d
.sync
+= self
.o_data
.eq(result
)
64 with m
.Else(): # o_p_busy is true, and something is in our buffer.
65 # Flush the buffer to the output port.
66 m
.d
.sync
+= self
.o_n_stb
.eq(1)
67 m
.d
.sync
+= self
.o_data
.eq(self
.r_data
)
68 # ignore input, since o_p_busy is also true.
69 # also clear stall condition, declare register to be empty.
70 m
.d
.sync
+= self
.o_p_busy
.eq(0)
72 # (i_n_busy) is true here: previous stage is busy
73 with m
.Elif(o_n_stbn
): # next stage being told "not busy"
74 m
.d
.sync
+= self
.o_n_stb
.eq(self
.i_p_stb
)
75 m
.d
.sync
+= self
.o_p_busy
.eq(0) # Keep the buffer empty
76 # Apply the logic to the input data, and set the output data
77 m
.d
.sync
+= self
.o_data
.eq(result
)
79 # (i_n_busy) and (o_n_stb) both true:
80 with m
.Elif(i_p_stb_o_p_busyn
):
81 # If next stage *is* busy, and not stalled yet, accept input
82 m
.d
.sync
+= self
.o_p_busy
.eq(self
.i_p_stb
& self
.o_n_stb
)
84 with m
.If(o_p_busyn
): # not stalled
85 # turns out that from all of the above conditions, just
86 # always put result into buffer if not busy
87 m
.d
.sync
+= self
.r_data
.eq(result
)
92 return [self
.i_p_stb
, self
.i_n_busy
, self
.i_data
,
94 self
.o_n_stb
, self
.o_p_busy
, self
.o_data
99 #yield dut.i_p_rst.eq(1)
100 yield dut
.i_n_busy
.eq(1)
101 yield dut
.o_p_busy
.eq(1)
104 #yield dut.i_p_rst.eq(0)
105 yield dut
.i_n_busy
.eq(0)
106 yield dut
.i_data
.eq(5)
107 yield dut
.i_p_stb
.eq(1)
109 yield dut
.i_data
.eq(7)
111 yield dut
.i_data
.eq(2)
113 yield dut
.i_n_busy
.eq(1)
114 yield dut
.i_data
.eq(9)
116 yield dut
.i_p_stb
.eq(0)
117 yield dut
.i_data
.eq(12)
119 yield dut
.i_data
.eq(32)
120 yield dut
.i_n_busy
.eq(0)
127 if __name__
== '__main__':
129 vl
= rtlil
.convert(dut
, ports
=dut
.ports())
130 with
open("test_bufpipe.il", "w") as f
:
132 run_simulation(dut
, testbench(dut
), vcd_name
="test_bufpipe.vcd")