Compare commits

..

No commits in common. "058191de55e38205b0b474995c546443e793376c" and "9939a226c8196a68058cfe99e6ed0e9f50e935d7" have entirely different histories.

7 changed files with 135 additions and 300 deletions

View File

@ -98,8 +98,7 @@
<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_sim('%IVERILOGPOSTURI%')"><span class="text">Icarus</span></button>
<button onclick="send_to_sim('%VERILATORPOSTURI%')"><span class="text">Verilator</span></button>
<button onclick="send_to_icarus()"><span class="text">Icarus</span></button>
<!-- button><span class="text">Verilator</span></button -->
</div>
@ -124,9 +123,9 @@
enableLiveAutocompletion : true
});
const log_area = document.getElementById('log');
function send_to_sim(uri) {
function send_to_icarus() {
log_area.innerHTML = "Please wait...";
fetch(uri,
fetch('%IVERILOGPOSTURI%',
{
method: 'POST',
headers: {

1
_web_server/server/iverilog Symbolic link
View File

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

View File

@ -24,17 +24,22 @@
(define INDEX-FILE "index.html")
(define DELETE-WORK-DIR #t)
(define TOP-MODULE "testbench")
(define SNIPPET-FILE "code.sv")
(define IVERILOG-METATOP-FILE "top_iverilog.sv")
(define VERILATOR-CPP-FILE "top_verilator.cpp")
(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 IVERILOG-EXE "iverilog")
(define VVP-EXE "vvp")
(define VERILATR-EXE "verilator")
(define SIM-SV-FILE "testbench.sv")
(define SIM-VC-FILE "testbench.vc")
(define SIM-EXE-FILE "testbench.out")
(define SNIPPET-FILE "code.sv")
(define URI-IVERILOG "iverilog")
(define URI-VERILATOR "verilator")
(define URI-SAVE-CODE "save")
(define URI-SAVEAS-CODE "saveas")
@ -45,16 +50,6 @@
(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")))
@ -240,6 +235,21 @@
(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
;;;
@ -257,86 +267,52 @@
(map string-trim-both paths))
"/")))))
(define (wrap-exe exe wrapper)
(format "~a~a" (if wrapper (format "~a " wrapper) "") exe))
;;;
;;; 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))
;;;
;;; Make workdir with sources and command file. Common part
;;; Returns work directory path string, verilog file name
;;; and command file name.
;;; Make workdir with sources and command file
;;; Returns work directory path string
;;;
(define* (make-sim-workdir code base top)
(define (make-sim-workdir simulator code top base)
(let* ((work-dir (mkdtemp (path+ base (format "work-~a-XXXXXX" (current-time)))))
(verilog-file (path+ work-dir (format "~a.sv" top)))
(command-file (path+ work-dir (format "~a.vc" top))))
(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)))
(with-output-to-file verilog-file (cut display code))
(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 ()
(cond
((eq? simulator 'iverilog)
(println "~a" verilog-file)
(println "~a" metatop-file)
(println "~a" dump-file)
(println "+define+TESTBENCH")
(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))))
(println "+timescale+1ps/1ps")))))
work-dir))
;;;
;;; Compile sources and execute simulation with Icarus Verilog
;;; Returns (values status log)
;;;
(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))))
(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)))
;; Compile
(let ((cmdline (format "~a -g2012 -s __~a__ -o ~a -c~a"
(wrap-exe IVERILOG-EXE iverilog-wrap)
top exe-file command-file)))
(let ((cmdline (format "~a -g2012 -o ~a -c~a" iverilog-exe exe-file command-file)))
(let-values (((status out)
(system-to-string cmdline)))
(let ((compile-log
@ -345,7 +321,7 @@
(values status compile-log)
;; Execute
(let ((cmdline (format "~a -N ~a" (wrap-exe VVP-EXE vvp-wrap) exe-file)))
(let ((cmdline (format "~a -N ~a" vvp-exe exe-file)))
(let-values (((status out)
(system-to-string cmdline)))
(let ((execution-log
@ -353,61 +329,37 @@
(values status (string-append compile-log execution-log)))))))))))
;;;
;;; Compile sources and execute simulation with Verilator
;;; Returns (values status log)
;;; Get iverilog version
;;;
(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)))
(define (iverilog-version iverilog-exe)
(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))))))))))
(system-to-string-list
(format "~a -V" iverilog-exe))))
(if (and (zero? status)
(not (null? out)))
(car out)
"Unknown")))
;;;
;;; Execute simulation
;;;
(define* (exec-sim simulator code base top #:key
(vvp-wrap "") (iverilog-wrap "") (metatop "")
(verilator-wrap "") (verilator-sim-wrap "") (verilator-cpp ""))
(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
(((work-dir status log)
(((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))))
(exec-sim-iverilog work-dir vvp-exe iverilog-exe))
;; 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)))))
(values -1 "No simulator found!\n")))))
(if (not work-dir)
("ERROR: Unknown simulator")
(begin
;; Delete work dir
(when DELETE-WORK-DIR
(delete-recursive work-dir))
@ -416,19 +368,9 @@
(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")))
(if (zero? status)
" succesfully"
" with errors"))))))))
;;;
;;; Get storage dir from URI
@ -486,43 +428,38 @@
(define (make-page-handler host root index-file
work-base stor-base
max-code-size
iverilog-wrap vvp-wrap
verilator-wrap verilator-sim-wrap)
vvp-exe iverilog-exe)
(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" ,(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))
`(("IVERILOGPOSTURI" ,iverilog-post-uri)
("SAVECODEURI" ,savecode-post-uri)
("SAVEASURI" ,saveas-post-uri)
("HELPSTRING",
(string-concatenate
(insert-between
`("Verilog Playground by Punzik (c) 2022"
""
,(format "Icarus: ~a"
(app-version (wrap-exe IVERILOG-EXE iverilog-wrap) "-V"))
,(format "Verilator: ~a"
(app-version (wrap-exe VERILATR-EXE verilator-wrap)))
(iverilog-version iverilog-exe))
,(format "Verilator: ~a" "TODO")
""
"Rules:"
"0. Don't fool around ;)"
"1. The top module must be named 'testbench'."
"2. The top module for the Verilator must have an input clock signal."
"1. (TODO) The top module must be named 'testbench'."
"2. (TODO) 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"))))))
(iverilog-metatop
(call-with-input-file IVERILOG-METATOP-FILE get-string-all))
(verilator-cpp
(call-with-input-file VERILATOR-CPP-FILE get-string-all)))
"\\n")))))))
(lambda (request request-body)
(let (;; Requested resource path
@ -612,27 +549,8 @@
(exec-sim 'iverilog
(sanitize-verilog code)
work-base
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)
#:vvp-exe vvp-exe
#:iverilog-exe iverilog-exe)
#:content-type 'text/plain))
;; Save snippet
@ -690,10 +608,8 @@
(-> " -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.")
(-> " --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: ./")
@ -702,11 +618,6 @@
(-> "")
(-> "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
@ -716,10 +627,8 @@
'(("port" #\p) required)
'(("host" #\s) required)
'(("root" #\r) required)
'(("vvp-wrap") required)
'(("iverilog-wrap") required)
'(("verilator-wrap") required)
'(("verilator-sim-wrap") required)
'(("vvp-exe") required)
'(("iverilog-exe") required)
'(("max-len") required)
'(("work-base") required)
'(("stor-base") required)
@ -730,10 +639,8 @@
(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-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")))
(vvp (string-trim (or (option-get opts "vvp-exe") "vvp")))
(iverilog (string-trim (or (option-get opts "iverilog-exe") "iverilog")))
(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") "./")))
@ -753,10 +660,6 @@
(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)
@ -765,7 +668,5 @@
(run-server
(make-page-handler host root INDEX-FILE
work-base stor-base
max-code-size
iverilog-wrap vvp-wrap
verilator-wrap verilator-sim-wrap)
max-code-size vvp iverilog)
'http `(#:host ,addr #:port ,port)))))))

View File

@ -1,12 +1,14 @@
#!/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 KILL 5 "$@"
exec timeout -v -s INT 5 "$exe" "$@"
fi

View File

@ -1,11 +0,0 @@
`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

@ -1,58 +0,0 @@
#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;
}

1
_web_server/server/vvp Symbolic link
View File

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