From 058191de55e38205b0b474995c546443e793376c Mon Sep 17 00:00:00 2001 From: Nikolay Puzanov Date: Sat, 3 Dec 2022 18:36:50 +0300 Subject: [PATCH] Add Verilator support --- _web_server/server/index.html | 7 +- _web_server/server/iverilog | 1 - _web_server/server/playground-server.scm | 351 +++++++++++------- .../server/{run-restricted => restrict} | 6 +- _web_server/server/top_iverilog.sv | 11 + _web_server/server/top_verilator.cpp | 58 +++ _web_server/server/vvp | 1 - 7 files changed, 295 insertions(+), 140 deletions(-) delete mode 120000 _web_server/server/iverilog rename _web_server/server/{run-restricted => restrict} (73%) create mode 100644 _web_server/server/top_iverilog.sv create mode 100644 _web_server/server/top_verilator.cpp delete mode 120000 _web_server/server/vvp diff --git a/_web_server/server/index.html b/_web_server/server/index.html index f343f9c..f441102 100644 --- a/_web_server/server/index.html +++ b/_web_server/server/index.html @@ -98,7 +98,8 @@ Sim: - + + @@ -123,9 +124,9 @@ enableLiveAutocompletion : true }); const log_area = document.getElementById('log'); - function send_to_icarus() { + function send_to_sim(uri) { log_area.innerHTML = "Please wait..."; - fetch('%IVERILOGPOSTURI%', + fetch(uri, { method: 'POST', headers: { diff --git a/_web_server/server/iverilog b/_web_server/server/iverilog deleted file mode 120000 index 7d894b5..0000000 --- a/_web_server/server/iverilog +++ /dev/null @@ -1 +0,0 @@ -run-restricted \ No newline at end of file diff --git a/_web_server/server/playground-server.scm b/_web_server/server/playground-server.scm index 2858158..a2c20e3 100755 --- a/_web_server/server/playground-server.scm +++ b/_web_server/server/playground-server.scm @@ -24,22 +24,17 @@ (define INDEX-FILE "index.html") (define DELETE-WORK-DIR #t) -(define DEFAULT-CODE - (string-append - "`timescale 10ps/10ps\n\n" - "module test;\n" - " initial begin\n" - " $display(\"Hello world!\");\n" - " $finish();\n" - " end\n" - "endmodule\n")) - -(define SIM-SV-FILE "testbench.sv") -(define SIM-VC-FILE "testbench.vc") -(define SIM-EXE-FILE "testbench.out") +(define TOP-MODULE "testbench") (define SNIPPET-FILE "code.sv") +(define IVERILOG-METATOP-FILE "top_iverilog.sv") +(define VERILATOR-CPP-FILE "top_verilator.cpp") + +(define IVERILOG-EXE "iverilog") +(define VVP-EXE "vvp") +(define VERILATR-EXE "verilator") (define URI-IVERILOG "iverilog") +(define URI-VERILATOR "verilator") (define URI-SAVE-CODE "save") (define URI-SAVEAS-CODE "saveas") @@ -50,6 +45,16 @@ (define LOG-LEVEL LOG-VERBOSE) +(define DEFAULT-CODE + (string-append + "`timescale 1ps/1ps\n\n" + (format "module ~a (input clock);\n" TOP-MODULE) + " initial begin\n" + " $display(\"Hello world!\");\n" + " $finish();\n" + " end\n" + "endmodule\n")) + (define (multistring . strings) (apply string-append (insert-between strings "\n"))) @@ -235,21 +240,6 @@ (code (regexp-substitute/global #f "\\$dump[a-z]*" code 'pre "$error" 'post))) code)) -;;; -;;; Get module name -;;; -(define (module-name code) - (let ((rx (make-regexp - "(^|\\s)module\\s*(\\s|(#\\(.*\\)\\s*))[a-zA-Z0-9_]+"))) - (let loop ((pos 0) - (modname #f)) - (let ((m (regexp-exec rx code pos))) - (if m - (loop (match:end m) (match:substring m)) - (if modname - (match:substring (string-match "[a-zA-Z0-9_]+$" modname)) - #f)))))) - ;;; ;;; Concatenate path elements and remove duplicate slashes ;;; @@ -267,52 +257,86 @@ (map string-trim-both paths)) "/"))))) -;;; -;;; Create dump module -;;; -(define (create-dump-module path modname top) - (define (-> . fmt) (display (apply format fmt)) (newline)) - (let ((filename (path+ path (format "~a.v" modname)))) - (with-output-to-file filename - (lambda () - (-> "`timescale 1ps/1ps") - (-> "module ~a();" modname) - (-> " initial begin") - (-> " $dumpfile(\"~a/~a.vcd\");" path modname) - (-> " $dumpvars(0, ~a);" top) - (-> " end") - (-> "endmodule"))) - filename)) +(define (wrap-exe exe wrapper) + (format "~a~a" (if wrapper (format "~a " wrapper) "") exe)) ;;; -;;; Make workdir with sources and command file -;;; Returns work directory path string +;;; Make workdir with sources and command file. Common part +;;; Returns work directory path string, verilog file name +;;; and command file name. ;;; -(define (make-sim-workdir simulator code top base) +(define* (make-sim-workdir code base top) (let* ((work-dir (mkdtemp (path+ base (format "work-~a-XXXXXX" (current-time))))) - (verilog-file (path+ work-dir SIM-SV-FILE)) - (command-file (path+ work-dir SIM-VC-FILE)) - (dump-file (create-dump-module work-dir "dump" top))) + (verilog-file (path+ work-dir (format "~a.sv" top))) + (command-file (path+ work-dir (format "~a.vc" top)))) (with-output-to-file verilog-file (cut display code)) - (with-output-to-file command-file - (lambda () - (cond - ((eq? simulator 'iverilog) + (values work-dir verilog-file command-file))) + +;;; +;;; Create workdir for Icarus Verilog +;;; Returns directory path +;;; +(define* (make-iverilog-workdir code metatop base top) + (let-values (((work-dir verilog-file command-file) + (make-sim-workdir code base top))) + (let ((metatop-file (path+ work-dir (format "__~a__.sv" top)))) + (with-output-to-file metatop-file + (cut display (substitute metatop "@~a@" + `((WORKDIR ,work-dir) + (TOPMODULE ,top))))) + + (with-output-to-file command-file + (lambda () (println "~a" verilog-file) - (println "~a" dump-file) + (println "~a" metatop-file) (println "+define+TESTBENCH") - (println "+timescale+1ps/1ps"))))) + (println "+timescale+1ps/1ps")))) + work-dir)) + +;;; +;;; Create workdir for Verilator +;;; Returns directory path +;;; +(define* (make-verilator-workdir code cpp base top) + (let-values (((work-dir verilog-file command-file) + (make-sim-workdir code base top))) + (let ((cpp-file (path+ work-dir (format "~a.cpp" top)))) + (with-output-to-file cpp-file + (cut display (substitute cpp "@~a@" `((WORKDIR ,work-dir) + (TOPMODULE ,top))))) + + (with-output-to-file command-file + (lambda () + (println "+define+TESTBENCH") + (println "--timescale 1ps/1ps") + (println "--top-module ~a" top) + (println "--Mdir ~a" (path+ work-dir top)) + (println "-cc") + (println "-O2") + (println "-o ~a" top) + (println "--exe") + (println "--build") + (println "-sv") + (println "-Wno-WIDTH") + (println "+1800-2017ext+sv") + (println "--timing") + (println "--trace") + (println "--quiet-exit") + (println "~a" verilog-file) + (println "~a.cpp" top)))) work-dir)) ;;; ;;; Compile sources and execute simulation with Icarus Verilog ;;; Returns (values status log) ;;; -(define (exec-sim-iverilog work-dir vvp-exe iverilog-exe) - (let ((exe-file (path+ work-dir SIM-EXE-FILE)) - (command-file (path+ work-dir SIM-VC-FILE))) +(define (exec-sim-iverilog top work-dir iverilog-wrap vvp-wrap) + (let ((command-file (path+ work-dir (format "~a.vc" top))) + (exe-file (path+ work-dir (format "~a.out" top)))) ;; Compile - (let ((cmdline (format "~a -g2012 -o ~a -c~a" iverilog-exe exe-file command-file))) + (let ((cmdline (format "~a -g2012 -s __~a__ -o ~a -c~a" + (wrap-exe IVERILOG-EXE iverilog-wrap) + top exe-file command-file))) (let-values (((status out) (system-to-string cmdline))) (let ((compile-log @@ -321,13 +345,79 @@ (values status compile-log) ;; Execute - (let ((cmdline (format "~a -N ~a" vvp-exe exe-file))) + (let ((cmdline (format "~a -N ~a" (wrap-exe VVP-EXE vvp-wrap) exe-file))) (let-values (((status out) (system-to-string cmdline))) (let ((execution-log (exe-log-pretty cmdline status out))) (values status (string-append compile-log execution-log))))))))))) +;;; +;;; Compile sources and execute simulation with Verilator +;;; Returns (values status log) +;;; +(define (exec-sim-verilator top work-dir verilator-wrap verilator-sim-wrap) + ;; Compile + (let* ((command-file (path+ work-dir (format "~a.vc" top))) + (cmdline (format "~a -f ~a" + (wrap-exe VERILATR-EXE verilator-wrap) + command-file))) + (let-values (((status out) + (system-to-string cmdline))) + (let ((compile-log + (exe-log-pretty cmdline status out))) + (if (not (zero? status)) + (values status compile-log) + + ;; Execute + (let ((cmdline (wrap-exe (path+ work-dir (format "~a/~a" top top)) + verilator-sim-wrap))) + (let-values (((status out) + (system-to-string cmdline))) + (let ((execution-log + (exe-log-pretty cmdline status out))) + (values status (string-append compile-log execution-log)))))))))) + +;;; +;;; Execute simulation +;;; +(define* (exec-sim simulator code base top #:key + (vvp-wrap "") (iverilog-wrap "") (metatop "") + (verilator-wrap "") (verilator-sim-wrap "") (verilator-cpp "")) + (let-values + (((work-dir status log) + (cond + ;; Run Icarus Verilog + ((eq? simulator 'iverilog) + (let ((work-dir (make-iverilog-workdir code metatop base top))) + (let-values (((status log) + (exec-sim-iverilog top work-dir iverilog-wrap vvp-wrap))) + (values work-dir status log)))) + + ;; Run Verilator + ((eq? simulator 'verilator) + (let ((work-dir (make-verilator-workdir code verilator-cpp base top))) + (let-values (((status log) + (exec-sim-verilator top work-dir verilator-wrap verilator-sim-wrap))) + (values work-dir status log)))) + + ;; Inknown simulator + (else + (values #f #f #f))))) + + (if (not work-dir) + ("ERROR: Unknown simulator") + (begin + ;; Delete work dir + (when DELETE-WORK-DIR + (delete-recursive work-dir)) + + ;; Return log + (string-append + log + (format "-----------------\nSimulation complete~a\n" + (if (zero? status) " succesfully"" with errors"))))))) + ;;; ;;; Get app version ;;; @@ -340,38 +430,6 @@ (car out) "Unknown"))) -;;; -;;; Execute simulation -;;; -(define* (exec-sim simulator code base - #:key - (vvp-exe "vvp") - (iverilog-exe "iverilog")) - (let ((top (module-name code))) - (if (not top) - "Error: No module declaration\n" - (let ((work-dir (make-sim-workdir simulator code top base))) - (let-values - (((status log) - (cond - ((eq? simulator 'iverilog) - (exec-sim-iverilog work-dir vvp-exe iverilog-exe)) - - (else - (values -1 "No simulator found!\n"))))) - - ;; Delete work dir - (when DELETE-WORK-DIR - (delete-recursive work-dir)) - - ;; Return log - (string-append - log - (format "-----------------\nSimulation complete~a\n" - (if (zero? status) - " succesfully" - " with errors")))))))) - ;;; ;;; Get storage dir from URI ;;; @@ -428,40 +486,43 @@ (define (make-page-handler host root index-file work-base stor-base max-code-size - vvp-exe iverilog-exe - verilator-exe) + iverilog-wrap vvp-wrap + verilator-wrap verilator-sim-wrap) (let* ((root-path (split-and-decode-uri-path root)) (root (encode-and-join-uri-path root-path)) (iverilog-path (append root-path `(,URI-IVERILOG))) + (verilator-path (append root-path `(,URI-VERILATOR))) (savecode-path (append root-path `(,URI-SAVE-CODE))) (saveas-path (append root-path `(,URI-SAVEAS-CODE))) - (iverilog-post-uri (encode-and-join-uri-path iverilog-path)) - (savecode-post-uri (encode-and-join-uri-path savecode-path)) - (saveas-post-uri (encode-and-join-uri-path saveas-path)) (index-html (read-template-text index-file - `(("IVERILOGPOSTURI" ,iverilog-post-uri) - ("SAVECODEURI" ,savecode-post-uri) - ("SAVEASURI" ,saveas-post-uri) + `(("IVERILOGPOSTURI" ,(encode-and-join-uri-path iverilog-path)) + ("VERILATORPOSTURI" ,(encode-and-join-uri-path verilator-path)) + ("SAVECODEURI" ,(encode-and-join-uri-path savecode-path)) + ("SAVEASURI" ,(encode-and-join-uri-path saveas-path)) ("HELPSTRING", (string-concatenate (insert-between `("Verilog Playground by Punzik (c) 2022" "" ,(format "Icarus: ~a" - (app-version iverilog-exe "-V")) + (app-version (wrap-exe IVERILOG-EXE iverilog-wrap) "-V")) ,(format "Verilator: ~a" - (app-version verilator-exe)) + (app-version (wrap-exe VERILATR-EXE verilator-wrap))) "" "Rules:" "0. Don't fool around ;)" - "1. (TODO) The top module must be named 'testbench'." - "2. (TODO) The top module for the Verilator must have an input clock signal." + "1. The top module must be named 'testbench'." + "2. The top module for the Verilator must have an input clock signal." "3. Code size should not exceed 10000 characters." "4. Code execution time no longer than 5 seconds.") - "\\n"))))))) + "\\n")))))) + (iverilog-metatop + (call-with-input-file IVERILOG-METATOP-FILE get-string-all)) + (verilator-cpp + (call-with-input-file VERILATOR-CPP-FILE get-string-all))) (lambda (request request-body) (let (;; Requested resource path @@ -551,8 +612,27 @@ (exec-sim 'iverilog (sanitize-verilog code) work-base - #:vvp-exe vvp-exe - #:iverilog-exe iverilog-exe) + TOP-MODULE + #:metatop iverilog-metatop + #:vvp-wrap vvp-wrap + #:iverilog-wrap iverilog-wrap) + #:content-type 'text/plain)) + + ;; Run verilator simulation + ((equal? path verilator-path) + (logger LOG-DBG "Request verilator simulation") + + (when ref-stor-dir + (save-to-storage (path+ stor-base ref-stor-dir) code)) + + (make-response + (exec-sim 'verilator + (sanitize-verilog code) + work-base + TOP-MODULE + #:verilator-wrap verilator-wrap + #:verilator-sim-wrap verilator-sim-wrap + #:verilator-cpp verilator-cpp) #:content-type 'text/plain)) ;; Save snippet @@ -606,21 +686,27 @@ (-> "Start Verilog playground WEB server") (-> "") (-> "Options:") - (-> " -a, --addr ADDR Listen on ADDR address. Default: 127.0.0.1") - (-> " -p, --port PORT Listen on PORT port. Default: 8080") - (-> " -s, --host URL Run on URL hostname. Default: http://127.0.0.1:8080") - (-> " -r, --root URN Service location root. Default: ''") - (-> " --ivverilog-exe PATH Set Icarus Verilog compiler executable. Default: iverilog") - (-> " --vvp-exe PATH Set Icarus Verilog interpreter executable. Default: vvp") - (-> " --verilator-exe PATH Set Icarus Verilog interpreter executable. Default: verilator") - (-> " --max-len LEN Set maximum code size in symbols. Default: 0 (infinite)") - (-> " --work-base PATH Set work base path. Default: ./") - (-> " --stor-base PATH Set snippets storage path. Default: ./") - (-> " --log-level LEVEL Set log level from 0 (quiet) to 10 (verbose). Default: 1./") - (-> " -h, --help Print this message and exit") + (-> " -a, --addr ADDR Listen on ADDR address. Default: 127.0.0.1") + (-> " -p, --port PORT Listen on PORT port. Default: 8080") + (-> " -s, --host URL Run on URL hostname. Default: http://127.0.0.1:8080") + (-> " -r, --root URN Service location root. Default: ''") + (-> " --iverilog-wrap PATH Icarus compiler wrapper.") + (-> " --vvp-wrap PATH Icarus Verilog interpreter wrapper.") + (-> " --verilator-wrap PATH Verilator compiler wrapper.") + (-> " --verilator-sim-wrap PATH Verilator simulation executable wrapper.") + (-> " --max-len LEN Set maximum code size in symbols. Default: 0 (infinite)") + (-> " --work-base PATH Set work base path. Default: ./") + (-> " --stor-base PATH Set snippets storage path. Default: ./") + (-> " --log-level LEVEL Set log level from 0 (quiet) to 10 (verbose). Default: 1./") + (-> " -h, --help Print this message and exit") (-> "") (-> "Source code and issue tracker: "))))) +(define (string-trim-if-string str) + (if (string? str) + (string-trim str) + str)) + (define (main args) (debug-disable 'backtrace) (let-values @@ -630,9 +716,10 @@ '(("port" #\p) required) '(("host" #\s) required) '(("root" #\r) required) - '(("vvp-exe") required) - '(("iverilog-exe") required) - '(("verilator-exe") required) + '(("vvp-wrap") required) + '(("iverilog-wrap") required) + '(("verilator-wrap") required) + '(("verilator-sim-wrap") required) '(("max-len") required) '(("work-base") required) '(("stor-base") required) @@ -643,9 +730,10 @@ (port (string->number (string-trim (or (option-get opts "port") "8080")))) (host (string-trim (or (option-get opts "host") "http://127.0.0.1:8080"))) (root (string-trim (or (option-get opts "root") ""))) - (vvp (string-trim (or (option-get opts "vvp-exe") "vvp"))) - (iverilog (string-trim (or (option-get opts "iverilog-exe") "iverilog"))) - (verilator (string-trim (or (option-get opts "verilator-exe") "verilator"))) + (vvp-wrap (string-trim-if-string (option-get opts "vvp-wrap"))) + (iverilog-wrap (string-trim-if-string (option-get opts "iverilog-wrap"))) + (verilator-wrap (string-trim-if-string (option-get opts "verilator-wrap"))) + (verilator-sim-wrap (string-trim-if-string (option-get opts "verilator-sim-wrap"))) (max-code-size (string->number (string-trim (or (option-get opts "max-len") "0")))) (work-base (string-trim (or (option-get opts "work-base") "./"))) (stor-base (string-trim (or (option-get opts "stor-base") "./"))) @@ -665,9 +753,10 @@ (set! LOG-LEVEL log-level) (logger LOG-INFO "Listen on '~a' port '~a'" addr port) (logger LOG-INFO "Server URL: '~a/~a'" host root) - (logger LOG-INFO "iverilog: '~a'" iverilog) - (logger LOG-INFO "vvp: '~a'" vvp) - (logger LOG-INFO "verilator: '~a'" verilator) + (logger LOG-INFO "iverilog wrapper: '~a'" iverilog-wrap) + (logger LOG-INFO "vvp wrapper: '~a'" vvp-wrap) + (logger LOG-INFO "verilator compiler wrapper: '~a'" verilator-wrap) + (logger LOG-INFO "verilator simulator wrapper: '~a'" verilator-sim-wrap) (logger LOG-INFO "Max code size: ~a" max-code-size) (logger LOG-INFO "Work base path: '~a'" work-base) (logger LOG-INFO "Storage base path: '~a'" stor-base) @@ -677,6 +766,6 @@ (make-page-handler host root INDEX-FILE work-base stor-base max-code-size - vvp iverilog - verilator) + iverilog-wrap vvp-wrap + verilator-wrap verilator-sim-wrap) 'http `(#:host ,addr #:port ,port))))))) diff --git a/_web_server/server/run-restricted b/_web_server/server/restrict similarity index 73% rename from _web_server/server/run-restricted rename to _web_server/server/restrict index e3c7fdf..2f8b683 100755 --- a/_web_server/server/run-restricted +++ b/_web_server/server/restrict @@ -1,14 +1,12 @@ #!/usr/bin/env bash -exe=$(basename $0) - if [ -z "$DONOTUSEFIREJAIL" ]; then exec firejail \ --quiet --noprofile \ --rlimit-cpu=5 \ --rlimit-as=250m \ --rlimit-fsize=250k \ - "$exe" "$@" + "$@" else - exec timeout -v -s INT 5 "$exe" "$@" + exec timeout -v -s KILL 5 "$@" fi diff --git a/_web_server/server/top_iverilog.sv b/_web_server/server/top_iverilog.sv new file mode 100644 index 0000000..8194dde --- /dev/null +++ b/_web_server/server/top_iverilog.sv @@ -0,0 +1,11 @@ +`timescale 1ps/1ps + +module __@TOPMODULE@__; + logic clock = 1'b0; + initial forever #(5ns) clock = ~clock; + @TOPMODULE@ @TOPMODULE@ (clock); + initial begin + $dumpfile("@WORKDIR@/@TOPMODULE@.vcd"); + $dumpvars(0, @TOPMODULE@); + end +endmodule diff --git a/_web_server/server/top_verilator.cpp b/_web_server/server/top_verilator.cpp new file mode 100644 index 0000000..bc6ba50 --- /dev/null +++ b/_web_server/server/top_verilator.cpp @@ -0,0 +1,58 @@ +#include "V@TOPMODULE@.h" + +#include +#include +#include + +#define DUMPFILE "@WORKDIR@/@TOPMODULE@.vcd" +#define CLOCK_HALF_PERIOD 5000 + +int main(int argc, char **argv) +{ + VerilatedContext *ctx = new VerilatedContext; + ctx->commandArgs(argc, argv); + + /* Create model instance */ + V@TOPMODULE@ *top = new V@TOPMODULE@(ctx); + +#if (VM_TRACE == 1) + VerilatedVcdC *vcd = new VerilatedVcdC; + ctx->traceEverOn(true); + top->trace(vcd, 99); + vcd->open(DUMPFILE); +#endif + + top->clock = 0; + + /* ---- Evaluation loop ---- */ + for (;;) { + /* Eval */ + top->eval(); + + /* Trace steady-state values */ +#if (VM_TRACE == 1) + if (vcd) vcd->dump(ctx->time()); +#endif + + /* Break exactly after calling $finish */ + if (ctx->gotFinish()) break; + + /* Clock event */ + ctx->timeInc(CLOCK_HALF_PERIOD); + top->clock = top->clock ? 0 : 1; + } + + top->final(); + printf("[%lu] Stop simulation\n", ctx->time()); + +#if (VM_TRACE == 1) + if (vcd) { + vcd->close(); + delete vcd; + } +#endif + + delete top; + delete ctx; + return 0; +} diff --git a/_web_server/server/vvp b/_web_server/server/vvp deleted file mode 120000 index 7d894b5..0000000 --- a/_web_server/server/vvp +++ /dev/null @@ -1 +0,0 @@ -run-restricted \ No newline at end of file