@@ -22,34 +22,77 @@ load(
2222 ":compress.bzl" , "compress_bitstream" ,
2323)
2424
25- VivadoConstraintInfo = provider (
25+ VivadoIPInfo = provider (
2626 fields = {
27- "srcs" : provider_field (Artifact ),
27+ "checkpoint" : provider_field (Artifact ),
28+ "xci" : provider_field (Artifact ),
29+ "name" : provider_field (str , default = "" ),
2830 }
2931)
3032
31- VivadoCheckpointInfo = provider (
32- fields = {
33- "checkpoint" : provider_field (Artifact ),
34- "name" :provider_field (str , default = "" ),
33+
34+ def _vivado_ip (ctx ):
35+ """ We're doing some machinations here to try to get a checkpoint and xci file generated by Vivado that we can cache
36+ Vivado's a bit annoying in that it just generates piles of files and you can't strictly specify them. What we want
37+ here is the checkpoint (.dcp) and the xci file so we can read those into the project later. When using read_ip,
38+ Vivado should figure out a checkpoint exists and not rebuild it if it doesn't need to do so.
39+ """
40+ module_name = ctx .attrs .module_name
41+ tcl = ctx .attrs .tcl
42+ part = ctx .attrs .part
43+
44+ dir_name = "{}_ip/{}" .format (module_name , module_name )
45+
46+ # generate a tcl file that sets up an IP compile.
47+ # this wraps the user's script to get outputs we can use.
48+ vivado_ip_tcl = ctx .actions .declare_output ("{}.tcl" .format (module_name ))
49+ py_ip_tcl_gen = ctx .attrs ._vivado_ip [RunInfo ]
50+ cmd = cmd_args ()
51+ cmd .add (py_ip_tcl_gen )
52+ cmd .add ("--output" , vivado_ip_tcl .as_output ())
53+ ctx .actions .run (cmd , category = "vivado_ip_tcl_{}_gen" .format (module_name ), allow_cache_upload = True )
54+
55+ # Now we have a tcl file that we can run in vivado to generate the IP.
56+ # Make buck2 expect a checkpoint file and an xci file in a subdirectory of our choosing.
57+ checkpoint = ctx .actions .declare_output (dir_name , "{}.dcp" .format (module_name ))
58+ xci = ctx .actions .declare_output (dir_name , "{}.xci" .format (module_name ))
59+ # Run the vivado command
60+ # Since the tcl above will be the same all the time, we need this action to be re-run
61+ # if the inputs change.
62+ cmd = cmd_args (hidden = [tcl , module_name , part ])
3563
36- })
64+ cmd .add (ctx .attrs ._toolchain [RunInfo ])
65+ cmd .add ("-mode" , "batch" )
66+ cmd .add ("-source" , vivado_ip_tcl )
67+ # This is a bit of a hack. Vivado is going to generate these (and more!) regardless of what we
68+ # specify here but this makes buck2 happy since we're "using" the outputs.
69+ cmd .add ("-tclargs" , module_name , checkpoint .as_output (), xci .as_output (), tcl , part )
3770
71+ ctx .actions .run (cmd , category = "vivado_ip_gen" , allow_cache_upload = True )
3872
39- def _vivado_constraint_impl (ctx ):
40- # No dep management to worry about so just collect the things
41- # and return a ConstraintProvider
42- return VivadoConstraintInfo (
43- srcs = ctx .attrs .srcs
44- )
73+ return [
74+ VivadoIPInfo (checkpoint = checkpoint , xci = xci , name = module_name ),
75+ DefaultInfo (default_output = xci ),
76+ ]
4577
46- vivado_constraint = rule (
47- impl = _vivado_constraint_impl ,
78+ vivado_ip = rule (
79+ impl = _vivado_ip ,
4880 attrs = {
49- "srcs" : attrs .list (attrs .source (doc = "Expected one or more XDC sources" )),
81+ "module_name" : attrs .string (),
82+ "tcl" : attrs .source (doc = "TCL file to generate the IP. expected args are module_name arg[0], output_dir arg[1]" ),
83+ "part" : attrs .string (doc = "Vivado-compatible FPGA string" ),
84+ "_vivado_ip" : attrs .exec_dep (
85+ doc = "Generate a Vivado ip generation tcl for this project" ,
86+ default = "root//tools/vivado_ip:vivado_ip" ,
87+ ),
88+ "_toolchain" : attrs .toolchain_dep (
89+ doc = "Vivado" ,
90+ default = "toolchains//:vivado" ,
91+ ),
5092 }
5193)
5294
95+
5396def _vivado_bitstream (ctx ):
5497 synth = synthesize (ctx )
5598 opt = optimize (ctx , synth )
@@ -88,13 +131,15 @@ def synthesize(ctx):
88131 # Get list of all sources from the dep tree via the tset in HDLFileInfo
89132 source_files_tset = ctx .attrs .top [HDLFileInfo ].set_all
90133 source_files = source_files_tset .project_as_json ("json" , ordering = "postorder" )
134+ ip_xci = [x [VivadoIPInfo ].xci for x in ctx .attrs .ip if x .get (VivadoIPInfo )]
91135
92136 out_json = {
93137 "flow" : "synthesis" ,
94138 "part" : ctx .attrs .part ,
95139 "max_threads" : 8 ,
96140 "synth_args" : "" ,
97141 "sources" : source_files ,
142+ "ip" : ip_xci ,
98143 "constraints" : constraints ,
99144 "top_name" : ctx .attrs .top_entity_name ,
100145 "pre_synth_tcl_files" : ctx .attrs .pre_synth_tcl_files ,
@@ -147,7 +192,7 @@ def synthesize(ctx):
147192
148193
149194 # Run vivado
150- ctx .actions .run (vivado , category = "vivado_{}" .format (flow ))
195+ ctx .actions .run (vivado , category = "vivado_{}" .format (flow ), allow_cache_upload = True )
151196 providers .append (DefaultInfo (default_output = checkpoint ))
152197 return providers
153198
@@ -182,7 +227,7 @@ def optimize(ctx, input_checkpoint):
182227
183228 # because we're using the inputs to generate a tcl that *just* lists them,
184229 # irrespective of their content, we make the checkpoint a hidden input
185- # so that if it changesthis step is re-run rather than just relying
230+ # so that if it changes, this step is re-run rather than just relying
186231 # on cache. We need this step to run if the input file content, or
187232 # constraint file content changes
188233
@@ -198,7 +243,7 @@ def optimize(ctx, input_checkpoint):
198243 vivado .add (debug_probes .as_output ())
199244
200245
201- ctx .actions .run (vivado , category = "vivado_{}" .format (flow ))
246+ ctx .actions .run (vivado , category = "vivado_{}" .format (flow ), allow_cache_upload = True )
202247 providers .append (DefaultInfo (default_output = out_checkpoint ))
203248 return providers
204249
@@ -236,7 +281,7 @@ def place(ctx, input_checkpoint, optimize=False):
236281 utilization_report .as_output ()
237282 )
238283
239- ctx .actions .run (vivado , category = "vivado_{}" .format (flow ))
284+ ctx .actions .run (vivado , category = "vivado_{}" .format (flow ), allow_cache_upload = True )
240285 providers .append (DefaultInfo (default_output = out_checkpoint ))
241286 return providers
242287
@@ -281,7 +326,7 @@ def route(ctx, input_checkpoint):
281326 )
282327
283328
284- ctx .actions .run (vivado , category = "vivado_{}" .format (flow ))
329+ ctx .actions .run (vivado , category = "vivado_{}" .format (flow ), allow_cache_upload = True )
285330 providers .append (DefaultInfo (default_output = out_checkpoint ))
286331 return providers
287332
@@ -305,7 +350,7 @@ def bitstream(ctx, input_checkpoint):
305350
306351 # because we're using the inputs to generate a tcl that *just* lists them,
307352 # irrespective of their content, we make the checkpoint a hidden input
308- # so that if it changesthis step is re-run rather than just relying
353+ # so that if it changes this step is re-run rather than just relying
309354 # on cache. We need this step to run if the input file content, or
310355 # constraint file content changes
311356
@@ -316,7 +361,7 @@ def bitstream(ctx, input_checkpoint):
316361 bitstream_bin .as_output (),
317362 )
318363
319- ctx .actions .run (vivado , category = "vivado_{}" .format (flow ))
364+ ctx .actions .run (vivado , category = "vivado_{}" .format (flow ), allow_cache_upload = True )
320365 providers .append (DefaultInfo (default_output = bitstream_bin ))
321366 return providers
322367
@@ -332,7 +377,7 @@ def _vivado_tcl_gen_common(ctx, flow, json):
332377 cmd .add ("--input" , in_json_file )
333378 cmd .add ("--output" , vivado_flow_tcl .as_output ())
334379
335- ctx .actions .run (cmd , category = "vivado_tcl_{}_gen" .format (flow ))
380+ ctx .actions .run (cmd , category = "vivado_tcl_{}_gen" .format (flow ), allow_cache_upload = True )
336381
337382 return vivado_flow_tcl , in_json_file
338383
@@ -361,6 +406,13 @@ vivado_bitstream = rule(
361406 attrs = {
362407 "top_entity_name" : attrs .string (),
363408 "top" : attrs .dep (doc = "Expected top HDL unit" ),
409+ "ip" : attrs .list (
410+ attrs .dep (
411+ doc = "Vivado IPs to include in the project. These are generated by vivado_ip" ,
412+ providers = [VivadoIPInfo ],
413+ ),
414+ default = [],
415+ ),
364416 "part" : attrs .string (doc = "Vivado-compatible FPGA string" ),
365417 "constraints" : attrs .list (attrs .source (doc = "Part constraint files" ), default = []),
366418 "pre_synth_tcl_files" : attrs .list (attrs .source (doc = "TCL files for project to source for things like ip" ), default = []),
0 commit comments