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