Skip to content

Commit b430e9c

Browse files
authored
Merge pull request #342 from pllab/synthesis-interface-update
Add option to emit 1-bit io after synthesis, and track io_map in PostSynth block.
2 parents 71a927a + 04114e5 commit b430e9c

File tree

3 files changed

+126
-15
lines changed

3 files changed

+126
-15
lines changed

pyrtl/passes.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -351,11 +351,14 @@ def _remove_unused_wires(block, keep_inputs=True):
351351
#
352352

353353

354-
def synthesize(update_working_block=True, block=None):
354+
def synthesize(update_working_block=True, merge_io_vectors=True, block=None):
355355
""" Lower the design to just single-bit "and", "or", "xor", and "not" gates.
356356
357-
:param update_working_block: Boolean specifying if working block update
358-
:param block: The block you want to synthesize
357+
:param update_working_block: Boolean specifying if working block should
358+
be set to the newly synthesized block.
359+
:param merge_io_wirevectors: if False, turn all N-bit IO wirevectors
360+
into N 1-bit IO wirevectors (i.e. don't maintain interface).
361+
:param block: The block you want to synthesize.
359362
:return: The newly synthesized block (of type PostSynthesisBlock).
360363
361364
Takes as input a block (default to working block) and creates a new
@@ -368,7 +371,10 @@ def synthesize(update_working_block=True, block=None):
368371
the individual bits and memories (read and write ports) which
369372
require the reassembly and disassembly of the wirevectors immediately
370373
before and after. These are the only two places where 'c' and 's' ops
371-
should exist.
374+
should exist. If merge_io_vectors is False, then these individual
375+
bits are not reassembled and disassembled before and after, and so no
376+
'c' and 's' ops will exist. Instead, they will be named <name>[n],
377+
where n is the bit number of original wire to which it corresponds.
372378
373379
The block that results from synthesis is actually of type
374380
"PostSynthesisBlock" which contains a mapping from the original inputs
@@ -384,7 +390,9 @@ def synthesize(update_working_block=True, block=None):
384390

385391
block_out = PostSynthBlock()
386392
# resulting block should only have one of a restricted set of net ops
387-
block_out.legal_ops = set('~&|^nrwcsm@')
393+
block_out.legal_ops = set('~&|^nrwm@')
394+
if merge_io_vectors:
395+
block_out.legal_ops.update(set('cs'))
388396
wirevector_map = {} # map from (vector,index) -> new_wire
389397

390398
with set_working_block(block_out, no_sanity_check=True):
@@ -399,6 +407,11 @@ def synthesize(update_working_block=True, block=None):
399407
('>', _basic_gt)]:
400408
net_transform(_replace_op(op, fun), block_in)
401409

410+
# This is a map from the cloned io wirevector created in copy_block,
411+
# to the original io wirevector found in block_pre. We use it to create
412+
# the block_out.io_map that is returned to the user.
413+
orig_io_map = {temp: orig for orig, temp in block_in.io_map.items()}
414+
402415
# Next, create all of the new wires for the new block
403416
# from the original wires and store them in the wirevector_map
404417
# for reference.
@@ -409,21 +422,31 @@ def synthesize(update_working_block=True, block=None):
409422
new_val = (wirevector.val >> i) & 0x1
410423
new_wirevector = Const(bitwidth=1, val=new_val)
411424
elif isinstance(wirevector, (Input, Output)):
412-
new_wirevector = WireVector(name="tmp_" + new_name, bitwidth=1)
425+
if merge_io_vectors:
426+
new_wirevector = WireVector(name="tmp_" + new_name, bitwidth=1)
427+
else:
428+
# Creating N 1-bit io wires for a given single N-bit io wire.
429+
new_name = wirevector.name
430+
if len(wirevector) > 1:
431+
new_name += '[' + str(i) + ']'
432+
new_wirevector = wirevector.__class__(name=new_name, bitwidth=1)
433+
block_out.io_map[orig_io_map[wirevector]] = new_wirevector
413434
else:
414435
new_wirevector = wirevector.__class__(name=new_name, bitwidth=1)
415436
wirevector_map[(wirevector, i)] = new_wirevector
416437

417438
# Now connect up the inputs and outputs to maintain the interface
418-
for wirevector in block_in.wirevector_subset(Input):
419-
input_vector = Input(name=wirevector.name, bitwidth=len(wirevector))
420-
for i in range(len(wirevector)):
421-
wirevector_map[(wirevector, i)] <<= input_vector[i]
422-
for wirevector in block_in.wirevector_subset(Output):
423-
output_vector = Output(name=wirevector.name, bitwidth=len(wirevector))
424-
output_bits = [wirevector_map[(wirevector, i)]
425-
for i in range(len(output_vector))]
426-
output_vector <<= concat_list(output_bits)
439+
if merge_io_vectors:
440+
for wirevector in block_in.wirevector_subset(Input):
441+
input_vector = Input(name=wirevector.name, bitwidth=len(wirevector))
442+
for i in range(len(wirevector)):
443+
wirevector_map[(wirevector, i)] <<= input_vector[i]
444+
block_out.io_map[orig_io_map[wirevector]] = [input_vector]
445+
for wirevector in block_in.wirevector_subset(Output):
446+
output_vector = Output(name=wirevector.name, bitwidth=len(wirevector))
447+
output_bits = [wirevector_map[(wirevector, i)] for i in range(len(output_vector))]
448+
output_vector <<= concat_list(output_bits)
449+
block_out.io_map[orig_io_map[wirevector]] = [output_vector]
427450

428451
# Now that we have all the wires built and mapped, walk all the blocks
429452
# and map the logic to the equivalent set of primitives in the system

pyrtl/transform.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ def copy_block(block=None, update_working_block=True):
194194
for net in block_in.logic:
195195
_copy_net(block_out, net, temp_wv_map, mems)
196196
block_out.mem_map = mems
197+
block_out.io_map = {io: w for io, w in temp_wv_map.items() if isinstance(io, (Input, Output))}
197198

198199
if update_working_block:
199200
set_working_block(block_out)

tests/test_passes.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,93 @@ def test_mux_simulation(self):
7070
self.check_trace('r 04213756\n')
7171

7272

73+
class TestIOInterfaceSynthesis(unittest.TestCase):
74+
def setUp(self):
75+
pyrtl.reset_working_block()
76+
a, b = pyrtl.input_list('a/4 b/4')
77+
o = pyrtl.Output(5, 'o')
78+
o <<= a + b
79+
80+
def check_merged_names(self, block):
81+
inputs = block.wirevector_subset(pyrtl.Input)
82+
outputs = block.wirevector_subset(pyrtl.Output)
83+
self.assertEqual({w.name for w in inputs}, {'a', 'b'})
84+
self.assertEqual({w.name for w in outputs}, {'o'})
85+
86+
def check_unmerged_names(self, block):
87+
inputs = block.wirevector_subset(pyrtl.Input)
88+
outputs = block.wirevector_subset(pyrtl.Output)
89+
self.assertEqual(
90+
{w.name for w in inputs},
91+
{'a[0]', 'a[1]', 'a[2]', 'a[3]', 'b[0]', 'b[1]', 'b[2]', 'b[3]'}
92+
)
93+
self.assertEqual(
94+
{w.name for w in outputs},
95+
{'o[0]', 'o[1]', 'o[2]', 'o[3]', 'o[4]'}
96+
)
97+
98+
def test_synthesize_merged_io_names_correct(self):
99+
pyrtl.synthesize()
100+
self.check_merged_names(pyrtl.working_block())
101+
102+
def test_synthesize_merged_io_mapped_correctly(self):
103+
old_io = pyrtl.working_block().wirevector_subset((pyrtl.Input, pyrtl.Output))
104+
pyrtl.synthesize()
105+
new_io = pyrtl.working_block().wirevector_subset((pyrtl.Input, pyrtl.Output))
106+
for oi in old_io:
107+
for ni in new_io:
108+
if oi.name == ni.name:
109+
self.assertEqual(pyrtl.working_block().io_map[oi], [ni])
110+
111+
def test_synthesize_merged_io_simulates_correctly(self):
112+
pyrtl.synthesize()
113+
sim = pyrtl.Simulation()
114+
sim.step_multiple({
115+
'a': [4, 6, 2, 3],
116+
'b': [2, 9, 11, 4],
117+
})
118+
output = six.StringIO()
119+
sim.tracer.print_trace(output, compact=True)
120+
self.assertEqual(
121+
output.getvalue(),
122+
'a 4623\n'
123+
'b 29114\n'
124+
'o 615137\n'
125+
)
126+
127+
def test_synthesize_unmerged_io_names_correct(self):
128+
pyrtl.synthesize(merge_io_vectors=False)
129+
self.check_unmerged_names(pyrtl.working_block())
130+
131+
def test_synthesize_unmerged_io_mapped_correctly(self):
132+
old_io = pyrtl.working_block().wirevector_subset((pyrtl.Input, pyrtl.Output))
133+
pyrtl.synthesize()
134+
new_io = pyrtl.working_block().wirevector_subset((pyrtl.Input, pyrtl.Output))
135+
for oi in old_io:
136+
for ni in new_io:
137+
if ni.name.startswith(oi.name):
138+
self.assertIn(ni, pyrtl.working_block().io_map[oi])
139+
140+
def test_synthesize_unmerged_io_simulates_correctly(self):
141+
pyrtl.synthesize(merge_io_vectors=False)
142+
sim = pyrtl.Simulation()
143+
for (a, b) in [(4, 2), (6, 9), (2, 11), (3, 4)]:
144+
args = {}
145+
for ix in range(4):
146+
args['a[' + str(ix) + ']'] = (a >> ix) & 1
147+
args['b[' + str(ix) + ']'] = (b >> ix) & 1
148+
sim.step(args)
149+
expected = a + b
150+
for ix in range(5):
151+
out = sim.inspect('o[' + str(ix) + ']')
152+
self.assertEqual(out, (expected >> ix) & 1)
153+
154+
def test_synthesize_does_not_update_working_block(self):
155+
synth_block = pyrtl.synthesize(update_working_block=False, merge_io_vectors=False)
156+
self.check_merged_names(pyrtl.working_block())
157+
self.check_unmerged_names(synth_block)
158+
159+
73160
class TestMultiplierSynthesis(unittest.TestCase):
74161
def setUp(self):
75162
pyrtl.reset_working_block()

0 commit comments

Comments
 (0)