suoto / hdl_checker

Repurposing existing HDL tools to help writing better code
GNU General Public License v3.0
187 stars 22 forks source link

Add support for verilator #67

Open suoto opened 4 years ago

rafaelnp commented 4 years ago

I suppose the base_builder.py and ghdl.py would be a good starting point, would they?

suoto commented 4 years ago

Yes, the tool independent logic should sit on base_builder.py, while <tool_name>.py should have only specifics. (At least that's the goal...)

I'll add verilator to the docker image so we can run all tests from there (generating the docker file needs some manual stuff still....)

rafaelnp commented 4 years ago

I add some initial code to detect verilator and its version. So far I was not successful, mainly due to my poor python skills. Do you have any suggestions for debugging the project?

suoto commented 4 years ago

If you're running via ./run_tests.sh or tox, logs emitted by the loggers will be inside .tox/<environment>/log/tests.log, where <environment> is one of py27-linux, py27-windows, py37-linux or py37-windows.

The main test for the tools is done inside hdl_checker/tests/test_builders.py.

The way the tests have been setup is to have each tool's binary executable inside a specific directory (in the Docker container that'd be /builders), so tests can insert and remove them from the execution path. To add verilator to the container, you could create an image based off of suoto/hdl_checker_tests:latest and just add that (it can be consolidated later) and update references to paths and available builders on tox.ini, hdl_checker/tests/__init__.py#L389 and hdl_checker/builder_utils.py#L203.

rafaelnp commented 4 years ago

Do you suggest/have, beside the corresponding projects documentation, any tutorial for docker and tox? I am not acquainted with both, specially the latter.

suoto commented 4 years ago

Not really, I usually look how other projects do things. The testing scripts should ideally be transparent, although there's no guide on that atm...

HDL Checker testing is admittedly not the most straightforward. In summary:

  1. run_tests.sh script wraps docker run with tox arguments and some info about the host environment to avoid permission issues, then calls the entry point script
  2. .ci/scripts/docker_entry_point.sh uses the args passed previously to create a user with the same name and user/group IDs, then calls tox
  3. tox config is given by tox.ini, which defines
    1. 4 environments: py27-linux, py27-windows, py37-linux and py37-windows
    2. Dependencies needed for testing only
    3. The actual test commands, the main one being .ci/scripts/run_tests.py
  4. .ci/scripts/run_tests.py sets up Python logging and code coverage and calls nose2, which will then call the tests defined inside hdl_checker/tests/

Arguments passed to run_tests.sh will be passed on to tox, which can pass arguments to .ci/scripts/run_tests.py and indirectly to nose2, so you could write something like

$ ./run_tests.sh -e py37-linux -- hdl_checker.tests.test_database --fail-fast -v
                 |-----------|  A |---------------------------------------------|
                   tox args     |    arguments passed to .ci/scripts/run_tests.py
                               arg
                            separator

And run tests defined in hdl_checker.tests.test_database on the Python 3.7 @ Linux env. Additionally, --fail-fast -v will stop the tests in the first error (nose2's default is to run all and then report errors if any).

Because run_tests.sh wraps tox, commands below are equivalent:

# Listing tox envs from within docker
$ ./run_tests.sh -l
py27-linux
py27-windows
py37-linux
py37-windows

# Equivalent to the above, but runs tox locally
$ tox -l
py27-linux
py27-windows
py37-linux
py37-windows

Hope that helps!

rafaelnp commented 4 years ago

Thanks for the tips, I am making some progress here. I forked the project and I am working on a branch called verilator.

rafaelnp commented 4 years ago

I was able to get a basic parsing for verilator to work, but the results are not shown on neovim. The _mapLibrary and _creatyLibrary methods just have a pass, Verilog and SystemVerilog provide all the language features direclty built-in in, no additional library declarations/includes are needed. Maybe tis issue is related to it. It would be nice if you could take a look and give me your feedback. Let me know if you need more infos.

The commit you can find here.

Below the logging for the committed code:

INFO    | 11:15:30 | hdl_checker.database @ addSource():150 Thread-3 |  Adding /home/rnp/tmp/simple_mux/simple_mux.v, library=None, flags=(single=(), dependencies=())
DEBUG   | 11:15:30 | hdl_checker.database @ _parseSource():420 Thread-3 |   Parsing /home/rnp/tmp/simple_mux/simple_mux.v
DEBUG   | 11:15:30 | hdl_checker.lsp @ _handleUiInfo():122 Thread-3 |   UI info: Added 1 sources (workspace=<pyls.workspace.Workspace object at 0x7f681d2b0850>)
DEBUG   | 11:15:30 | hdl_checker.base_server @ _updateConfigIfNeeded():227 Thread-3 |   Updated config file to WatchedFile(path=Path('/tmp/hdl_checker_project_pid294155.json'), last_read=1582625729.6559722, origin=<ConfigFileOrigin.generated: 'generated'>)
INFO    | 11:15:30 | hdl_checker.database @ getLibrary():389 Thread-3 | Library for '/home/rnp/tmp/simple_mux/simple_mux.v' not set, inferring it
DEBUG   | 11:15:30 | hdl_checker.database @ _inferLibraryForPath():485 Thread-3 |   Units defined here in /home/rnp/tmp/simple_mux/simple_mux.v: ["VerilogDesignUnit(name='simple_mux', type=entity, owner='/home/rnp/tmp/simple_mux/simple_mux.v')"]
DEBUG   | 11:15:30 | hdl_checker.database @ getLibrariesReferredByUnit():527 Thread-3 | Searching for uses of VerilogIdentifier('simple_mux')
INFO    | 11:15:30 | hdl_checker.database @ _inferLibraryForPath():499 Thread-3 |   Couldn't work out a library for path /home/rnp/tmp/simple_mux/simple_mux.v
DEBUG   | 11:15:30 | hdl_checker.database @ getDependenciesUnits():680 Thread-3 |   Searching {Path('/home/rnp/tmp/simple_mux/simple_mux.v')} resulted in dependencies: set()
DEBUG   | 11:15:30 | hdl_checker.database @ getDependenciesUnits():704 Thread-3 |   Search paths: set()
INFO    | 11:15:30 | hdl_checker.database @ _getBuildSequence():818 Thread-3 |  Nothing more to do after 0 steps
DEBUG   | 11:15:30 | hdl_checker.base_server @ _getBuilderMessages():422 Thread-3 | Built dependencies, now actually building '/home/rnp/tmp/simple_mux/simple_mux.v'
INFO    | 11:15:30 | hdl_checker.builders.verilator @ build():426 Thread-3 |    Forcing build of /home/rnp/tmp/simple_mux/simple_mux.v
INFO    | 11:15:30 | hdl_checker.builders.verilator @ _buildSource():137 Thread-3 | detected valid file: '/home/rnp/tmp/simple_mux/simple_mux.v'
INFO    | 11:15:30 | hdl_checker.builders.verilator @ _buildVerilog():163 Thread-3 |    verilator command line: '['verilator_bin', '--lint-only', '-Wpedantic', '-Wall', '/home/rnp/tmp/simple_mux/simple_mux.v']'
DEBUG   | 11:15:30 | hdl_checker.utils @ runShellCommand():259 Thread-3 |   verilator_bin --lint-only -Wpedantic -Wall /home/rnp/tmp/simple_mux/simple_mux.v
DEBUG   | 11:15:30 | hdl_checker.utils @ runShellCommand():275 Thread-3 |   Command '['verilator_bin', '--lint-only', '-Wpedantic', '-Wall', '/home/rnp/tmp/simple_mux/simple_mux.v']' failed with error code 1.
Stdout:
%Error: /home/rnp/tmp/simple_mux/simple_mux.v:15: syntax error, unexpected reg, expecting IDENTIFIER
 reg bar;
 ^~~
%Error: /home/rnp/tmp/simple_mux/simple_mux.v:32: syntax error, unexpected ';'
  bar == 0;
          ^
%Error: Exiting due to 2 error(s)
DEBUG   | 11:15:30 | hdl_checker.base_server @ _buildAndHandleRebuilds():454 Thread-3 | Had no rebuilds for /home/rnp/tmp/simple_mux/simple_mux.v
DEBUG   | 11:15:30 | hdl_checker.base_server @ _saveCache():286 Thread-3 |  Saving state to '/home/rnp/tmp/simple_mux/.hdl_checker/cache.json'

I am running verilator against the following file:

module simple_mux(
    input [1:0] x,
    input [4:0] y,
    input [11:0] in,
    input s,
    output reg [1:0] m
    );

    this_is_an_error

    reg bar;

    // the line bellow introduces a warning
    wire [7:0] foo = in[11:0] + 3'b1;

    always @(x or y or s)
    begin
        if (s == 0)
            m = y;
        else
            m = x;

        bar <= foo;
    end

    always @(s)
    begin
        bar == 0;
    end
endmodule: simple_mux

The project configuration file:

{
  "sources": [
    "/home/rnp/tmp/simple_mux/*.v",
  ],
}

My neovim + coc configuration:

    "hdlchecker": {
       "command": "hdl_checker",
       "args": ["--lsp", "--log-level", "DEBUG"],
       "filetypes": ["vhdl", "verilog", "systemverilog"]
    }

Version information:

suoto commented 4 years ago

First of all, sorry for the long time to get back on this.

I tried cloning your repo and could not get it do reproduce this, I'm guessing not everything has been committed.

By the log it seems that parsing the command's output is not matching anything. This line here says which command was run and its output (which looks alright), but after this there should be messages reporting the parsed reports.

DEBUG   | 11:15:30 | hdl_checker.utils @ runShellCommand():275 Thread-3 |   Command '['verilator_bin', '--lint-only', '-Wpedantic', '-Wall', '/home/rnp/tmp/simple_mux/simple_mux.v']' failed with error code 1.
Stdout:
%Error: /home/rnp/tmp/simple_mux/simple_mux.v:15: syntax error, unexpected reg, expecting IDENTIFIER
 reg bar;
 ^~~
%Error: /home/rnp/tmp/simple_mux/simple_mux.v:32: syntax error, unexpected ';'
  bar == 0;
          ^
%Error: Exiting due to 2 error(s)

The most helpful tool I found to get this right is https://regex101.com/ (make sure to select Python flavour, the default is PCRE, and to put the right flags). You can run the regex on sample text and make sure the groups are correct.

After some fiddling, I got this regex that seems to work:

  _stdout_message_scanner = re.compile(
      r"""
      ^%(?P<severity>((\w+\-\w+)|(\w+)))\:\s*
      (?P<filename>[^:]+\.s?v):
      (?P<line_number>\d+)\:\s*
      (?P<error_message>.*)""",
      flags=re.VERBOSE,
  ).finditer

There's also other suggestions (as it seems your local copy is newer these might be out of date already)

If you prefer asking stuff on Gitter.im, I think it's easier to help in smaller things really.

rafaelnp commented 4 years ago

In fact, one file was not committed, my bad. I committed it here, it sets verilator as first builder to be used, you should be able to reproduce it now.

The committed code is parsing and matching the warnings and error. I used the same web site (https://regex101.com/) to generate the regex, the only difference between what I committed is the last line, to match warnings as well.

    _stdout_message_scanner = re.compile(
        r"""^\%(?P<severity>((\w+\-\w+)|(\w+)))\:
        \s*(?P<filename>\w+\.(v|sv))\:
        (?P<line_number>\d+)\:\s*
        (?P<error_message>(.*\n.*\n.*)|(.*\n.*))""",
        flags=re.VERBOSE,
    ).finditer

What I could observe, is that verilator stops on the errors, once the are corrected it shows the warnings. The logging error messages are the matched contents from the regex. The problem is to pass this information (regex matching) correctly to neovim to show it to the user. So far I did not succeed, if I make any progress, I shall write it here..

Regarding your suggestions:

  1. I used runShellCommand("verilator --version", shell=True) because the verilator file is a perl script, not an ELF binary. Otherwise I get an Command '['verilator', '--version']' failed with [Errno 8] Exec format error: 'verilator'

  2. I did not know this mypy, I will take a look into it.

  3. I shall remove the COPY verilator $BUILDERS/verilator

  4. Thanks for the Gittter.im link, I alread signed in.

suoto commented 4 years ago

What I could observe, is that verilator stops on the errors, once the are corrected it shows the warnings. The logging error messages are the matched contents from the regex. The problem is to pass this information (regex matching) correctly to neovim to show it to the user. So far I did not succeed, if I make any progress, I shall write it here

That's were I think the unit tests really help, they usually catch the small details that might be breaking (esp chasing which stderr log file is the latest one).

I used runShellCommand("verilator --version", shell=True) because the verilator file is a perl script, not an ELF binary. Otherwise I get an Command '['verilator', '--version']' failed with [Errno 8] Exec format error: 'verilator'

I saw somewhere you were using verilator_bin, so I assumed they were the same.

rafaelnp commented 4 years ago

The unit tests are a great idea, thanks for pointing it out. I am gonna dive into it. Nevertheless, it would speed up this part of development process, to write a wiki page documenting the following points/steps:

I would gladly help in this effort. :)

Regarding the runShellCommand I am gonna change it for sure, but first I would like to get the verilator up and running.

rafaelnp commented 4 years ago

Following your suggestion, I started the playing with the unit tests, got 57% coverage according to Tox. I pushed these changes already. There is more to do for sure. :+1:

However I noticed, the _makeRecords method is not called, only the _buildSource, in the verilator builder. I suppose I am missing some concept/connection in the builder hierarchy, so the former method is not called. I took a look at the MSim, Ghdland Xvhdl classes and could not figure it out myself so far. Maybe you could shed some light on it? :-)