1
0
mirror of https://github.com/myhdl/myhdl.git synced 2024-12-14 07:44:38 +08:00

True case statment mapping support (#408)

* Add initial support for match/case in python, only available in 3.10 on

* Fixed testing so that only the match code is used on python 3.10 or above

* Add spport for enumerated types in match statments

* Rewrote the code to leverage the correct AST way of walking the case/match tree to build the correct RTL versions

* Removed enumerate where not used

* Removed enumerate where not used

* Move appropriate code into match_case definition

* Remove unused defs

* Remove unused defs

* Cleanup of redundant code, and replace some simple variable names with more representative names
This commit is contained in:
Dave Keeshan 2023-02-03 17:08:02 +00:00 committed by GitHub
parent 1dd830fbfc
commit 35bd903371
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 515 additions and 4 deletions

View File

@ -7,6 +7,12 @@ ANSI_GREEN=`tput setaf 2`
ANSI_CYAN=`tput setaf 6`
ANSI_RESET=`tput sgr0`
# Some tests contain python 3.10 syntax, they can even be presented to pytest to parse with the wrong python
PYV=$(shell python -c "import sys;t='{v[0]}{v[1]:02}'.format(v=list(sys.version_info[:2]));sys.stdout.write(t)")
ifeq ($(shell test $(PYV) -lt 310; echo $$?),0)
PYTEST_OPTS += --ignore-glob='*_py310.py'
endif
install:
python setup.py install
@ -33,7 +39,7 @@ release:
git push && git push --tags
clean:
rm -rf *.vhd *.v *.o *.log *.hex work/ cosimulation/icarus/myhdl.vpi
rm -rf *.vhd *.v *.o *.log *.vcd *.hex work/ cosimulation/icarus/myhdl.vpi
lint:
pyflakes myhdl/
@ -42,7 +48,7 @@ black:
black myhdl/
core:
@echo -e "\n${ANSI_CYAN}running test: $@ ${ANSI_RESET}"
pytest ./myhdl/test/core ${PYTEST_OPTS}
pytest -v ./myhdl/test/core ${PYTEST_OPTS}
iverilog_myhdl.vpi:
${MAKE} -C cosimulation/icarus myhdl.vpi
@ -61,7 +67,7 @@ iverilog_bugs:
iverilog: iverilog_cosim
@echo -e "\n${ANSI_CYAN}running test: $@ ${ANSI_RESET}"
pytest ./myhdl/test/conversion/general ./myhdl/test/conversion/toVerilog ./myhdl/test/bugs --sim iverilog ${PYTEST_OPTS}
pytest -v ./myhdl/test/conversion/general ./myhdl/test/conversion/toVerilog ./myhdl/test/bugs --sim iverilog ${PYTEST_OPTS}
ghdl_general:
pytest ./myhdl/test/conversion/general --sim ghdl ${PYTEST_OPTS}
@ -74,6 +80,6 @@ ghdl_bugs:
ghdl:
@echo -e "\n${ANSI_CYAN}running test: $@ ${ANSI_RESET}"
pytest ./myhdl/test/conversion/general ./myhdl/test/conversion/toVHDL ./myhdl/test/bugs --sim ghdl ${PYTEST_OPTS}
pytest -v ./myhdl/test/conversion/general ./myhdl/test/conversion/toVHDL ./myhdl/test/bugs --sim ghdl ${PYTEST_OPTS}
pytest: core iverilog ghdl

View File

@ -1441,6 +1441,78 @@ class _ConvertVisitor(ast.NodeVisitor, _ConversionMixin):
self.mapToCase(node)
else:
self.mapToIf(node)
def visit_Match(self, node):
self.write("case ")
self.visit(node.subject)
self.write(" is")
self.indent()
for case in node.cases:
case.subject = node.subject
self.visit(case)
self.dedent()
self.writeline()
self.write("end case;")
def visit_match_case(self, node):
self.writeline()
self.write("when ")
pattern = node.pattern
pattern.subject = node.subject
self.visit(pattern)
self.write(" => ")
self.indent()
# Write all the multiple assignment per case
for stmt in node.body:
self.writeline()
self.visit(stmt)
self.dedent()
def visit_MatchValue(self, node):
baseobj = self.getObj(node.subject)
item = node.value
obj = self.getObj(item)
if isinstance(obj, EnumItemType):
itemRepr = obj._toVHDL()
elif hasattr(baseobj, '_nrbits'):
itemRepr = self.BitRepr(item.value, baseobj)
else:
raise AssertionError("Unknown type %s " % (type(obj)))
self.write(itemRepr)
def visit_MatchSingleton(self, node):
raise AssertionError("Unsupported Match type %s " % (type(node)))
def visit_MatchSequence(self, node):
raise AssertionError("Unsupported Match type %s " % (type(node)))
def visit_MatchStar(self, node):
raise AssertionError("Unsupported Match type %s " % (type(node)))
def visit_MatchMapping(self, node):
raise AssertionError("Unsupported Match type %s " % (type(node)))
def visit_MatchClass(self, node):
for pattern in node.patterns:
pattern.subject = node.subject
self.visit(pattern)
def visit_MatchAs(self, node):
if node.name is None and node.pattern is None:
self.write("others")
else:
raise AssertionError("Unknown name %s or pattern %s" % (node.name, node.pattern))
def visit_MatchOr(self, node):
for i, pattern in enumerate(node.patterns):
pattern.subject = node.subject
self.visit(pattern)
if not i == len(node.patterns)-1:
self.write(" | ")
def mapToCase(self, node):
var = node.caseVar

View File

@ -1041,6 +1041,72 @@ class _ConvertVisitor(ast.NodeVisitor, _ConversionMixin):
else:
self.mapToIf(node)
def visit_Match(self, node):
self.write("case (")
self.visit(node.subject)
self.write(")")
self.indent()
for case in node.cases:
self.visit(case)
self.writeline()
self.dedent()
self.writeline()
self.write("endcase")
def visit_match_case(self, node):
pattern = node.pattern
self.visit(pattern)
self.write(": begin ")
self.indent()
# Write all the multiple assignment per case
for stmt in node.body:
self.writeline()
self.visit(stmt)
self.dedent()
self.writeline()
self.write("end")
def visit_MatchValue(self, node):
item = node.value
obj = self.getObj(item)
if isinstance(obj, EnumItemType):
itemRepr = obj._toVerilog()
else:
itemRepr = self.IntRepr(item.value, radix='hex')
self.write(itemRepr)
def visit_MatchSingleton(self, node):
raise AssertionError("Unsupported Match type %s " % (type(node)))
def visit_MatchSequence(self, node):
raise AssertionError("Unsupported Match type %s " % (type(node)))
def visit_MatchStar(self, node):
raise AssertionError("Unsupported Match type %s " % (type(node)))
def visit_MatchMapping(self, node):
raise AssertionError("Unsupported Match type %s " % (type(node)))
def visit_MatchClass(self, node):
for pattern in node.patterns:
self.visit(pattern)
def visit_MatchAs(self, node):
if node.name is None and node.pattern is None:
self.write("default")
else:
raise AssertionError("Unknown name %s or pattern %s" % (node.name, node.pattern))
def visit_MatchOr(self, node):
for i, pattern in enumerate(node.patterns):
self.visit(pattern)
if not i == len(node.patterns)-1:
self.write(" | ")
def mapToCase(self, node, *args):
var = node.caseVar
# self.write("// synthesis parallel_case")

View File

@ -0,0 +1,366 @@
import sys
from myhdl import *
import pytest
#SELOPTS = enum('SEL0', 'SEL1', 'SEL2', 'SEL3')
@block
def mux4a(
sel,
in0,
in1,
in2,
in3,
out0
):
@always_comb
def rtl():
if sel == 0:
out0.next = in0
elif sel == 1:
out0.next = in1
elif sel == 2:
out0.next = in2
else:
out0.next = in3
return instances()
@block
def mux4b(sel, in0, in1, in2, in3, out0):
@always_comb
def rtl():
match sel:
case 0:
out0.next = in0
case 1:
out0.next = in1
case 2:
out0.next = in2
case _:
out0.next = in3
return instances()
@block
def mux4c(sel, in0, in1, in3, out0):
@always_comb
def rtl():
match sel:
case 0:
out0.next = in0
case 1 | 2:
out0.next = in1
case _:
out0.next = in3
return instances()
t_opts = enum('SEL0', 'SEL1', 'SEL2', 'SEL3')
@block
def enumMux4a(
sel,
in0,
in1,
in2,
in3,
out0,
):
sel_enum = Signal(t_opts.SEL0)
@always_comb
def mapping():
if 0 == sel:
sel_enum.next = t_opts.SEL0
elif 1 == sel:
sel_enum.next = t_opts.SEL1
elif 2 == sel:
sel_enum.next = t_opts.SEL2
elif 3 == sel:
sel_enum.next = t_opts.SEL3
@always_comb
def rtl():
if sel_enum == t_opts.SEL0:
out0.next = in0
elif sel_enum == t_opts.SEL1:
out0.next = in1
elif sel_enum == t_opts.SEL2:
out0.next = in2
elif sel_enum == t_opts.SEL3:
out0.next = in3
return instances()
@block
def enumMux4b(
sel,
in0,
in1,
in2,
in3,
out0,
):
sel_enum = Signal(t_opts.SEL0)
@always_comb
def mapping():
if 0 == sel:
sel_enum.next = t_opts.SEL0
elif 1 == sel:
sel_enum.next = t_opts.SEL1
elif 2 == sel:
sel_enum.next = t_opts.SEL2
elif 3 == sel:
sel_enum.next = t_opts.SEL3
@always_comb
def rtl():
match sel_enum:
case t_opts.SEL0:
out0.next = in0
case t_opts.SEL1:
out0.next = in1
case t_opts.SEL2:
out0.next = in2
case _:
out0.next = in3
return instances()
t_fsma_opts = enum('IDLE', 'READ', 'WRITE', 'ERROR')
@block
def fsm4a(
clk,
rst,
rd,
wr,
):
smp = Signal(t_fsma_opts.IDLE)
@always(clk.posedge)
def rtl():
if rst:
smp.next = t_fsma_opts.IDLE
else:
match smp:
case t_fsma_opts.IDLE:
smp.next = t_fsma_opts.IDLE
if rd:
smp.next = t_fsma_opts.READ
if wr:
smp.next = t_fsma_opts.WRITE
case t_fsma_opts.READ:
smp.next = t_fsma_opts.IDLE
case t_fsma_opts.WRITE:
smp.next = t_fsma_opts.IDLE
case _:
smp.next = t_fsma_opts.IDLE
return instances()
@block
def fsm4b(
clk,
a,
b,
c,
z,
):
@always(clk.posedge)
def rtl():
match concat(a,b,c):
case intbv(0b111) :
z.next = 0x6
case intbv(0b101) | intbv(0b110) :
z.next = 0x7
case _:
z.next = 0x0
return instances()
@block
def muxBench0(setup=0):
clk = Signal(bool(0))
sel = Signal(intbv(0)[2:])
in0 = Signal(intbv(0)[4:])
in1 = Signal(intbv(0)[4:])
in2 = Signal(intbv(0)[4:])
in3 = Signal(intbv(0)[4:])
out0 = Signal(intbv(0)[4:])
@instance
def clkgen():
clk.next = 1
for i in range(400):
yield delay(10)
clk.next = not clk
@instance
def stimulus():
sel.next = 0x0
in0.next = 0xa
in1.next = 0xb
in2.next = 0xc
in3.next = 0xd
yield clk.posedge
yield clk.posedge
sel.next = 0x1
yield clk.posedge
sel.next = 0x2
yield clk.posedge
sel.next = 0x3
yield clk.posedge
sel.next = 0x0
yield clk.posedge
raise StopSimulation
@instance
def check():
yield clk.posedge
yield clk.posedge
assert out0 == 0xa
yield clk.posedge
assert out0 == 0xb
yield clk.posedge
assert out0 == 0xc
yield clk.posedge
assert out0 == 0xd
yield clk.posedge
assert out0 == 0xa
yield clk.posedge
if 0 == setup:
i_mux = mux4a(sel, in0, in1, in2, in3, out0)
elif 1 == setup:
i_mux = mux4b(sel, in0, in1, in2, in3, out0)
elif 2 == setup:
i_mux = enumMux4a(sel, in0, in1, in2, in3, out0)
else:
i_mux = enumMux4b(sel, in0, in1, in2, in3, out0)
return instances()
def test_mux4a_convert():
clk = Signal(bool(0))
sel = Signal(intbv(0)[2:])
in0 = Signal(intbv(0)[4:])
in1 = Signal(intbv(0)[4:])
in2 = Signal(intbv(0)[4:])
in3 = Signal(intbv(0)[4:])
out0 = Signal(intbv(0)[4:])
i_dut = mux4a(sel, in0, in1, in2, in3, out0)
assert i_dut.analyze_convert() == 0
def test_muxBench0():
sim = muxBench0(0)
sim.run_sim()
def test_muxBench0_convert():
i_dut = muxBench0(0)
assert i_dut.analyze_convert() == 0
#@pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
def test_mux4b_convert():
clk = Signal(bool(0))
sel = Signal(intbv(0)[2:])
in0 = Signal(intbv(0)[4:])
in1 = Signal(intbv(0)[4:])
in2 = Signal(intbv(0)[4:])
in3 = Signal(intbv(0)[4:])
out0 = Signal(intbv(0)[4:])
i_dut = mux4b(sel, in0, in1, in2, in3, out0)
assert i_dut.analyze_convert() == 0
#@pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
def test_mux4b_convert():
clk = Signal(bool(0))
sel = Signal(intbv(0)[2:])
in0 = Signal(intbv(0)[4:])
in1 = Signal(intbv(0)[4:])
in3 = Signal(intbv(0)[4:])
out0 = Signal(intbv(0)[4:])
i_dut = mux4c(sel, in0, in1, in3, out0)
assert i_dut.analyze_convert() == 0
#@pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
def test_muxBench1():
sim = muxBench0(1)
sim.run_sim()
#@pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
def test_muxBench1_convert():
i_dut = muxBench0(1)
assert i_dut.analyze_convert() == 0
def test_muxBench2():
sim = muxBench0(2)
sim.run_sim()
def test_muxBench2_convert():
i_dut = muxBench0(2)
assert i_dut.analyze_convert() == 0
def test_enumMux4a_convert():
clk = Signal(bool(0))
sel = Signal(intbv(0)[2:])
in0 = Signal(intbv(0)[4:])
in1 = Signal(intbv(0)[4:])
in2 = Signal(intbv(0)[4:])
in3 = Signal(intbv(0)[4:])
out0 = Signal(intbv(0)[4:])
i_dut = enumMux4a(sel, in0, in1, in2, in3, out0)
assert i_dut.analyze_convert() == 0
def test_muxBench3():
sim = muxBench0(3)
sim.run_sim()
def test_enumMux4b_convert():
clk = Signal(bool(0))
sel = Signal(intbv(0)[2:])
in0 = Signal(intbv(0)[4:])
in1 = Signal(intbv(0)[4:])
in2 = Signal(intbv(0)[4:])
in3 = Signal(intbv(0)[4:])
out0 = Signal(intbv(0)[4:])
i_dut = enumMux4b(sel, in0, in1, in2, in3, out0)
assert i_dut.analyze_convert() == 0
def test_fsm4a_convert():
clk = Signal(bool(0))
rst = Signal(intbv(0)[1:])
rd = Signal(intbv(0)[1:])
wr = Signal(intbv(0)[1:])
i_dut = fsm4a(clk, rst, rd, wr)
assert i_dut.analyze_convert() == 0
def test_fsm4b_convert():
clk = Signal(bool(0))
a = Signal(intbv(0)[1:])
b = Signal(intbv(0)[1:])
c = Signal(intbv(0)[1:])
z = Signal(intbv(0)[3:])
i_dut = fsm4b(clk, a, b, c, z)
assert i_dut.analyze_convert() == 0

View File

@ -1,3 +1,4 @@
pytest
pytest-xdist
pyflakes
black