milanmetal

[Python] VHDL Entity parser

Nov 7th, 2018
101
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.11 KB | None | 0 0
  1. import sys
  2. from jinjautil import render_testbench
  3. import datetime
  4. #
  5. # by Milan N.
  6. # Nov. 2018
  7. #
  8. '''
  9. This is parsed from .vhd file (mem_interface.vhd)
  10.  
  11. {'generic': [['M_DATA_WIDTH', 'integer', '16'],
  12.             ['M_ADDR_WIDTH', 'integer', '4'],
  13.             ['M_MODE', 'mem_mode', 'read']],
  14. 'port': [['clk_i', 'in', 'std_logic'],
  15.          ['waddr_i', 'in', 'std_logic_vector(M_ADDR_WIDTH-1 downto 0)'],
  16.          ['raddr_i', 'in', 'std_logic_vector(M_ADDR_WIDTH-1 downto 0)'],
  17.          ['re_i', 'in', 'std_logic'],
  18.          ['we_i', 'in', 'std_logic'],
  19.          ['data_i', 'in', 'std_logic_vector(M_DATA_WIDTH-1 downto 0)'],
  20.          ['data_o', 'out', 'std_logic_vector(M_DATA_WIDTH-1 downto 0)']]}
  21.  
  22. This script should print out something like:
  23. ------------------------------------------------------
  24.  
  25.     --VHDL Testbench generator stats --
  26.  
  27.     Parsed generics:
  28.     --------------------------------------------------
  29.     M_DATA_WIDTH         | integer         | 16
  30.     M_ADDR_WIDTH         | integer         | 4
  31.     M_MODE               | mem_mode        | read
  32.  
  33.  
  34.     Parsed ports:
  35.     --------------------------------------------------
  36.     clk_i                | in     | std_logic
  37.     waddr_i              | in     | std_logic_vector(M_ADDR_WIDTH-1 downto 0)
  38.     raddr_i              | in     | std_logic_vector(M_ADDR_WIDTH-1 downto 0)
  39.     re_i                 | in     | std_logic
  40.     we_i                 | in     | std_logic
  41.     data_i               | in     | std_logic_vector(M_DATA_WIDTH-1 downto 0)
  42.     data_o               | out    | std_logic_vector(M_DATA_WIDTH-1 downto 0)
  43.  
  44.  
  45.     --Recognized clock port: clk_i
  46.  
  47.     Testbench located in:
  48.     /home/milan/PSDS_VEZBE/psds-vezbe/psds_v2/3_2/mem_interface_tb.vhd
  49.  
  50. ------------------------------------------------------
  51.  
  52. --
  53. -- In order to make this thing work, there are simple rules for entities :
  54. -- 1. generic(
  55. --      <list of generics>
  56. -- );
  57. --
  58. -- 2. port (
  59. --      <list of ports>
  60. -- );
  61. --
  62. -- 3. Clock signal should contain "clk" in its name
  63. -- 4. Clock signal should be placed higher in port list
  64. --    to prevent other signals containing "clk" in its name to be
  65. --    recognized as clock signals as well.
  66. --
  67. -- This is simple parser and it detects generic( and port( as a starting point
  68. -- and a ); as a terminating point for a code lines appending process.
  69. --
  70. -- Original .vhd file:
  71.  
  72. entity mem_interface is
  73.  generic(
  74.    M_DATA_WIDTH    : integer := 16;
  75.    M_ADDR_WIDTH    : integer := 4;
  76.    M_MODE          : mem_mode := read
  77. );
  78.  
  79.  port (
  80.    clk_i   : in std_logic;
  81.    waddr_i  : in std_logic_vector(M_ADDR_WIDTH-1 downto 0);
  82.    raddr_i  : in std_logic_vector(M_ADDR_WIDTH-1 downto 0);
  83.    re_i    : in std_logic;
  84.    we_i    : in std_logic;
  85.    data_i : in std_logic_vector(M_DATA_WIDTH-1 downto 0);
  86.    data_o : out std_logic_vector(M_DATA_WIDTH-1 downto 0)
  87.    );
  88. end mem_interface;
  89.  
  90. '''
  91.  
  92.  
  93. def entity_extractor(lines, name):
  94.     entity_lines = []
  95.     appending = False
  96.  
  97.     for line in lines:
  98.         if line.startswith(f"entity {name}"):
  99.             appending = True
  100.             continue
  101.  
  102.         if line.startswith(f"end {name}"):
  103.             appending = False
  104.             continue
  105.  
  106.         if appending:
  107.             entity_lines.append(line)
  108.  
  109.     return entity_lines
  110.  
  111.  
  112. def extract_dependencies(lines):
  113.     pkglib_lines = []
  114.     appending = False
  115.  
  116.     for line in lines:
  117.         if line.startswith("use "):
  118.             appending = True
  119.  
  120.         if not line.startswith("use "):
  121.             appending = False
  122.  
  123.         if appending:
  124.             pkglib_lines.append(line)
  125.  
  126.     return pkglib_lines
  127.  
  128.  
  129. def extract_clock(ports):
  130.     ''' Simply check if there is a port with "clk" in its name
  131.        this gives me some naming flexibility... ("clk", "clk_i", ...)
  132.    '''
  133.     clk_port = False
  134.     for port in ports:
  135.         if "clk" in port[0]:
  136.             clk_port = port[0]
  137.             # there shouldnt be any problems if clock is listed
  138.             # at the top of port declaration
  139.             # this will break loop on first clk occurence
  140.             break
  141.  
  142.     return clk_port
  143.  
  144.  
  145. def extract(object_type, entity_lines):
  146.     tokens = {}
  147.     appending = False
  148.  
  149.     tokens[object_type] = []
  150.  
  151.     for line in entity_lines:
  152.         if line.startswith(f"{object_type}"):
  153.             appending = True
  154.             continue
  155.  
  156.         if line.startswith(f");"):
  157.             appending = False
  158.             continue
  159.  
  160.         if appending:
  161.             tokens[object_type].append(line)
  162.  
  163.     # split on first occurence of :, do not split on :=
  164.     split_lines = [line.split(":", 1) for line in tokens[object_type]]
  165.     stripped_lines = [[line.strip() for line in item] for item in split_lines]
  166.  
  167.     # split of splits...
  168.     line_chunks = []
  169.     if object_type is "generic":
  170.         line_chunks = [chunk[1].split(" ") for chunk in stripped_lines]
  171.  
  172.     if object_type is "port":
  173.         line_chunks = [chunk[1].split(" ", 1) for chunk in stripped_lines]
  174.  
  175.     # lets do some madjijanje
  176.     aa = [l.pop(0) for l in stripped_lines]
  177.     line_chunks = [[a, *line_chunk]
  178.                    for (a, line_chunk) in zip(aa, line_chunks)]
  179.  
  180.     # replace ; in all tokens that have it
  181.     line_chunks = [[a.replace(";", "") for a in k if a != ":="]
  182.                    for k in line_chunks]
  183.  
  184.     tokens[object_type] = line_chunks
  185.     return tokens
  186.  
  187.  
  188. def print_progress(objects):
  189.     ''' Prints information about extracted objects'''
  190.     # out buffer, contains rendered text
  191.     out = ""
  192.     print("\n\t--VHDL Testbench generator stats --")
  193.     if 'generic' in objects and objects['generic']:
  194.         print("\n\tParsed generics:")
  195.         print("\t" + 50 * "-")
  196.         for generic in objects['generic']:
  197.             out += "\t{:20s} | {:15s} | {:10s}\n".format(
  198.                 generic[0], generic[1], generic[2])
  199.  
  200.         print(out)
  201.  
  202.     out = ""
  203.     if 'port' in objects:
  204.         print("\n\tParsed ports:")
  205.         print("\t" + 50 * "-")
  206.         for port in objects['port']:
  207.             out += "\t{:20s} | {:6s} | {:30s}\n".format(
  208.                 port[0], port[1], port[2])
  209.  
  210.         print(out)
  211.  
  212.     if 'clock_port' in objects and objects['clock_port']:
  213.         print(f"\n\t--Recognized clock port: {objects['clock_port']}\n")
  214.  
  215.  
  216. def file_exists(tb_fname):
  217.     from pathlib import Path
  218.     my_file = Path(tb_fname)
  219.     if my_file.is_file():
  220.         return True
  221.  
  222.  
  223. def main(argv):
  224.     filename = None
  225.  
  226.     # to ease my debugging process
  227.     if len(argv) > 1:
  228.         ROOT = argv[0]
  229.         DIR = f"{ROOT}/{argv[1]}/{argv[2]}"
  230.         entity_name = argv[3]
  231.  
  232.         filename = f"{DIR}/{entity_name}.vhd"
  233.     else:
  234.         ROOT = "/home/milan/PSDS_VEZBE/psds-vezbe"
  235.         DIR = f"{ROOT}/psds_v2/3_2"
  236.         entity_name = "mem_interface"
  237.         filename = f"{DIR}/{entity_name}.vhd"
  238.  
  239.     with open(filename, 'r') as myfile:
  240.         data = myfile.readlines()
  241.  
  242.     lines = [line.replace('\n', '').replace('\t', '').strip() for line in data]
  243.  
  244.     entity = entity_extractor(lines, entity_name)
  245.  
  246.     dependencies = extract_dependencies(lines)
  247.  
  248.     # extract all tokens from these blocks
  249.     generics = extract("generic", entity)
  250.     ports = extract("port", entity)
  251.     clk_port = extract_clock(ports['port'])
  252.  
  253.     objects = {**generics, **ports, "clock_port": clk_port}
  254.  
  255.     tb_fname = f"{DIR}/{entity_name}_tb.vhd"
  256.  
  257.     now = datetime.datetime.now()
  258.     # Jinja template generator requires context in order
  259.     # to render variables into their template placeholders
  260.     context = {
  261.         "entity_name": entity_name,
  262.         "generic_constants": objects['generic'],
  263.         "ports": objects['port'],
  264.         "dependencies": dependencies,
  265.         "time": now,
  266.         "clk_period": 10,
  267.         "clock_name": clk_port
  268.     }
  269.  
  270.     if file_exists(tb_fname):
  271.         print(
  272.             f"\n\tTestbench file already exists! \n\tLocation: {tb_fname}\n\n")
  273.     else:
  274.         render_testbench(entity_name, context, tb_fname)
  275.  
  276.         # lets print out parsed objects
  277.         print_progress(objects)
  278.  
  279.         print(f"\tTestbench located in: \n\t{tb_fname}\n\n")
  280.  
  281.  
  282. if __name__ == "__main__":
  283.     # print(sys.argv)
  284.     main(sys.argv[1:])
Add Comment
Please, Sign In to add comment