Skip to content

Commit 2c20f5b

Browse files
Add VHDL support via GHDL
Signed-off-by: Michael Riegert <michael@eowyn.net>
1 parent 10e9a98 commit 2c20f5b

File tree

1 file changed

+56
-9
lines changed

1 file changed

+56
-9
lines changed

sphinxcontrib_hdl_diagrams/__init__.py

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,9 @@ class HDLDiagram(Directive):
165165
"hdl_diagram_output_format": ["svg", "png"],
166166
"hdl_diagram_skin": ["default"], # or path
167167
"hdl_diagram_yosys_script": ["default"], # or path
168-
"hdl_diagram_yosys": ["yowasp", "system"] # or path
168+
"hdl_diagram_yosys": ["yowasp", "system"], # or path
169+
"hdl_diagram_ghdl": ["built-in", "module"], # or path
170+
"hdl_diagram_ghdl_std": ["87", "93", "93c", "00", "02", "08"]
169171
}
170172

171173
def run(self):
@@ -237,18 +239,20 @@ def run(self):
237239
return [node]
238240

239241

240-
def run_yosys(src, cmd, yosys='yowasp'):
242+
def run_yosys(src, cmd, yosys='yowasp', options=''):
241243
if yosys == 'yowasp':
242244
import yowasp_yosys
243245
ycmd = ["-q", "-p", "{}".format(cmd), src]
246+
if options != '':
247+
ycmd.insert(0, options)
244248
print("Running YoWASP yosys: {}".format(ycmd))
245249
yowasp_yosys.run_yosys(ycmd)
246250
elif yosys == 'system':
247-
ycmd = "yosys -p '{cmd}' {src}".format(src=src, cmd=cmd)
251+
ycmd = "yosys {options} -p '{cmd}' {src}".format(options=options, src=src, cmd=cmd)
248252
print("Running yosys: {}".format(ycmd))
249253
subprocess.check_output(ycmd, shell=True)
250254
else:
251-
ycmd = "{yosys} -p '{cmd}' {src}".format(yosys=yosys, src=src, cmd=cmd)
255+
ycmd = "{yosys} {options} -p '{cmd}' {src}".format(options=options, yosys=yosys, src=src, cmd=cmd)
252256
print("Running yosys: {}".format(ycmd))
253257
subprocess.check_output(ycmd, shell=True)
254258

@@ -372,6 +376,28 @@ def nmigen_to_rtlil(fname, oname):
372376
cmd = "{python} {script} > {output}".format(python=sys.executable, script=fname, output=oname)
373377
subprocess.run(cmd, shell=True, check=True)
374378

379+
def vhdl_to_verilog(fname, oname, module, ghdl, ghdl_std, yosys):
380+
assert os.path.exists(fname)
381+
382+
if ghdl == "module":
383+
yosys_opt = "-m ghdl "
384+
elif ghdl == "built-in":
385+
yosys_opt = ""
386+
elif os.path.exists(ghdl):
387+
yosys_opt = "-m '{}'".format(ghdl)
388+
else:
389+
raise HDLDiagramError("hdl_diagram_ghdl can only be \"module\", \"built-in\", or "
390+
"a path to a ghdl-yosys-plugin shared library, not '{}'".format(ghdl))
391+
392+
output_dir = os.path.dirname(oname)
393+
os.makedirs(output_dir, exist_ok=True)
394+
cmd = "ghdl --std={std} {input} -e {module}; write_verilog {output}".format(
395+
std=ghdl_std,
396+
module=module,
397+
input=fname,
398+
output=oname
399+
)
400+
run_yosys('', cmd, yosys, options=yosys_opt)
375401

376402
def render_diagram(self, code, options, format, skin, yosys_script):
377403
# type: (nodes.NodeVisitor, unicode, Dict, unicode, unicode) -> Tuple[unicode, unicode]
@@ -384,16 +410,26 @@ def render_diagram(self, code, options, format, skin, yosys_script):
384410
relfn = posixpath.join(self.builder.imgpath, fname)
385411
outfn = path.join(self.builder.outdir, self.builder.imagedir, fname)
386412

413+
yosys = self.builder.config.hdl_diagram_yosys
414+
387415
if source_ext == '.py':
388416
module = 'top'
389417
ilfn = path.join(self.builder.outdir, self.builder.imagedir, options['outname'] + '.il')
390418
nmigen_to_rtlil(source_path, ilfn)
391419
source_path = ilfn
392420
elif source_ext == '.il' or source_ext == '.v':
393421
module = options['module']
422+
elif source_ext == '.vhd' or source_ext == '.vhdl':
423+
if yosys == "yowasp":
424+
raise HDLDiagramError("Cannot use YoWASP for VHDL (yet)")
425+
module = options['module']
426+
ilfn = path.join(self.builder.outdir, self.builder.imagedir, options['outname'] + '.v')
427+
vhdl_to_verilog(source_path, ilfn, module,
428+
self.builder.config.hdl_diagram_ghdl, self.builder.config.hdl_diagram_ghdl_std, yosys)
429+
source_path = ilfn
394430
else:
395431
raise HDLDiagramError("hdl_diagram_code file extension must be one of '.v', "
396-
"'.il', or '.py', but is %r" % source_ext)
432+
"'.il', '.py', '.vhd', or '.vhdl', but is %r" % source_ext)
397433

398434
if path.isfile(outfn):
399435
print('Exiting file:', outfn)
@@ -404,7 +440,6 @@ def render_diagram(self, code, options, format, skin, yosys_script):
404440
yosys_script = options['yosys_script'] if options['yosys_script'] is not None else yosys_script
405441
skin = options['skin'] if options['skin'] is not None else skin
406442

407-
yosys = self.builder.config.hdl_diagram_yosys
408443
yosys_options = HDLDiagram.global_variable_options["hdl_diagram_yosys"]
409444
if yosys not in yosys_options and not os.path.exists(yosys):
410445
raise HDLDiagramError("Yosys not found!")
@@ -441,6 +476,9 @@ def render_diagram_html(
441476
self, node, code, options, imgcls=None, alt=None):
442477
# type: (nodes.NodeVisitor, hdl_diagram, unicode, Dict, unicode, unicode, unicode) -> Tuple[unicode, unicode] # NOQA
443478

479+
diagram_error = False
480+
diagram_error_message = ""
481+
444482
yosys_script = self.builder.config.hdl_diagram_yosys_script
445483
if yosys_script != 'default' and not path.exists(yosys_script):
446484
raise HDLDiagramError("Yosys script file {} does not exist! Change hdl_diagram_yosys_script variable".format(yosys_script))
@@ -457,9 +495,16 @@ def render_diagram_html(
457495
fname, outfn = render_diagram(self, code, options, format, skin, yosys_script)
458496
except HDLDiagramError as exc:
459497
logger.warning('hdl_diagram code %r: ' % code + str(exc))
460-
raise nodes.SkipNode
461-
462-
if fname is None:
498+
diagram_error = True
499+
diagram_error_message = str(exc)
500+
# raise nodes.SkipNode
501+
502+
if diagram_error:
503+
self.body.append('<div class="admonition warning">'
504+
'<paragraph class="admonition-title">{title}: </paragraph>'
505+
'<paragraph>hdl_diagram code <code>{file}</code>: {message}</paragraph>'
506+
'</div>'.format(title="Warning", file=code, message=diagram_error_message))
507+
elif fname is None:
463508
self.body.append(self.encode(code))
464509
else:
465510
if alt is None:
@@ -568,4 +613,6 @@ def setup(app):
568613
app.add_config_value('hdl_diagram_skin', 'default', 'html')
569614
app.add_config_value('hdl_diagram_yosys_script', 'default', 'html')
570615
app.add_config_value('hdl_diagram_yosys', 'yowasp', 'html')
616+
app.add_config_value('hdl_diagram_ghdl', 'built-in', 'html')
617+
app.add_config_value('hdl_diagram_ghdl_std', '08', 'html')
571618
return {'version': '1.0', 'parallel_read_safe': True}

0 commit comments

Comments
 (0)