Compare commits

...

2 Commits

Author SHA1 Message Date
Nikolay Puzanov
058191de55 Add Verilator support 2022-12-03 18:36:50 +03:00
Nikolay Puzanov
f61bb7b980 Prepare to add Verilator 2022-12-03 13:52:43 +03:00
7 changed files with 299 additions and 134 deletions

View File

@ -98,7 +98,8 @@
<button onclick="save_code('%SAVECODEURI%')"><span class="text">Save (ctrl-s)</span></button>
<button onclick="save_code('%SAVEASURI%')"><span class="text">Save as new</span></button>
Sim:
<button onclick="send_to_icarus()"><span class="text">Icarus</span></button>
<button onclick="send_to_sim('%IVERILOGPOSTURI%')"><span class="text">Icarus</span></button>
<button onclick="send_to_sim('%VERILATORPOSTURI%')"><span class="text">Verilator</span></button>
<!-- button><span class="text">Verilator</span></button -->
</div>
@ -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: {

View File

@ -1 +0,0 @@
run-restricted

View File

@ -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,7 +345,7 @@
(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
@ -329,48 +353,82 @@
(values status (string-append compile-log execution-log)))))))))))
;;;
;;; Get iverilog version
;;; Compile sources and execute simulation with Verilator
;;; Returns (values status log)
;;;
(define (iverilog-version iverilog-exe)
(let-values (((status out)
(system-to-string-list
(format "~a -V" iverilog-exe))))
(if (and (zero? status)
(not (null? out)))
(car out)
"Unknown")))
(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
#: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))
(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))))
(else
(values -1 "No simulator found!\n")))))
;; 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))))
;; Delete work dir
(when DELETE-WORK-DIR
(delete-recursive work-dir))
;; Inknown simulator
(else
(values #f #f #f)))))
;; Return log
(string-append
log
(format "-----------------\nSimulation complete~a\n"
(if (zero? status)
" succesfully"
" with errors"))))))))
(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
;;;
(define* (app-version exe #:optional (option "--version"))
(let-values (((status out)
(system-to-string-list
(format "~a ~a" exe option))))
(if (and (zero? status)
(not (null? out)))
(car out)
"Unknown")))
;;;
;;; Get storage dir from URI
@ -428,38 +486,43 @@
(define (make-page-handler host root index-file
work-base stor-base
max-code-size
vvp-exe iverilog-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"
(iverilog-version iverilog-exe))
,(format "Verilator: ~a" "TODO")
(app-version (wrap-exe IVERILOG-EXE iverilog-wrap) "-V"))
,(format "Verilator: ~a"
(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
@ -549,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
@ -604,20 +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")
(-> " --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: <https://github.com/punzik/>")))))
(define (string-trim-if-string str)
(if (string? str)
(string-trim str)
str))
(define (main args)
(debug-disable 'backtrace)
(let-values
@ -627,8 +716,10 @@
'(("port" #\p) required)
'(("host" #\s) required)
'(("root" #\r) required)
'(("vvp-exe") required)
'(("iverilog-exe") required)
'(("vvp-wrap") required)
'(("iverilog-wrap") required)
'(("verilator-wrap") required)
'(("verilator-sim-wrap") required)
'(("max-len") required)
'(("work-base") required)
'(("stor-base") required)
@ -639,8 +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")))
(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") "./")))
@ -660,6 +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 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)
@ -668,5 +765,7 @@
(run-server
(make-page-handler host root INDEX-FILE
work-base stor-base
max-code-size vvp iverilog)
max-code-size
iverilog-wrap vvp-wrap
verilator-wrap verilator-sim-wrap)
'http `(#:host ,addr #:port ,port)))))))

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,58 @@
#include "V@TOPMODULE@.h"
#include <cstdint>
#include <verilated.h>
#include <verilated_vcd_c.h>
#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;
}

View File

@ -1 +0,0 @@
run-restricted