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:
parent
1dd830fbfc
commit
35bd903371
14
Makefile
14
Makefile
@ -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
|
@ -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
|
||||
|
@ -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")
|
||||
|
366
myhdl/test/conversion/general/test_match_py310.py
Normal file
366
myhdl/test/conversion/general/test_match_py310.py
Normal 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
|
||||
|
@ -1,3 +1,4 @@
|
||||
pytest
|
||||
pytest-xdist
|
||||
pyflakes
|
||||
black
|
||||
|
Loading…
x
Reference in New Issue
Block a user