Compare commits
43 Commits
47ec7690a5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3aee2d2f38 | ||
|
|
b84ec9a1c5 | ||
|
|
1e0bfb58cf | ||
|
|
e67558de14 | ||
|
|
54f7e2be54 | ||
|
|
cc605cce85 | ||
|
|
2f9f5b6dd1 | ||
|
|
cb8bc37dfe | ||
|
|
1e12d5d3e2 | ||
|
|
1bc6ec544d | ||
|
|
d04282e1c7 | ||
|
|
664b7b1f5c | ||
|
|
37b7a54d3b | ||
|
|
01c982fada | ||
|
|
931f4d8aa4 | ||
|
|
90905544d4 | ||
|
|
ab5db7f6e8 | ||
|
|
e8ba09ecab | ||
|
|
daa744f8b2 | ||
|
|
c91003e34b | ||
|
|
53412381fb | ||
|
|
536bde92df | ||
|
|
b9cd29ad87 | ||
|
|
c6c6744b22 | ||
|
|
42f22147ea | ||
|
|
b4507004b8 | ||
|
|
74e76d8131 | ||
|
|
058191de55 | ||
|
|
f61bb7b980 | ||
|
|
9939a226c8 | ||
|
|
185dcb350d | ||
|
|
28d86fb98d | ||
|
|
680e9f3aa5 | ||
|
|
ec02787747 | ||
|
|
b87a7e0c97 | ||
|
|
6a9959f98d | ||
|
|
61a16ad2cd | ||
|
|
9f4fbe2499 | ||
|
|
347dfa0af6 | ||
|
|
5a23aa8e0f | ||
|
|
171b821dbb | ||
|
|
0e4d5a2df3 | ||
|
|
43a2e51137 |
@@ -10,4 +10,11 @@ module testbench;
|
||||
initial begin
|
||||
$finish;
|
||||
end
|
||||
|
||||
`ifdef DUMP
|
||||
initial begin
|
||||
$dumpfile("testbench.fst");
|
||||
$dumpvars(0, testbench);
|
||||
end
|
||||
`endif
|
||||
endmodule
|
||||
|
||||
52
_web_server/Dockerfile
Normal file
52
_web_server/Dockerfile
Normal file
@@ -0,0 +1,52 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM ubuntu:22.10
|
||||
LABEL description="Verilog playground"
|
||||
|
||||
# Prepare OS
|
||||
RUN sed -i 's/^# *deb-src/deb-src/g' /etc/apt/sources.list
|
||||
RUN apt-get -y update \
|
||||
&& apt-get -y install guile-3.0 locales git
|
||||
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen \
|
||||
&& locale-gen
|
||||
RUN apt-get build-dep -y iverilog verilator
|
||||
|
||||
# Build latest Icarus Veriolog
|
||||
WORKDIR /build
|
||||
RUN git clone --depth 1 https://github.com/steveicarus/iverilog.git
|
||||
WORKDIR /build/iverilog
|
||||
RUN sh ./autoconf.sh && ./configure --prefix=/usr && make -j2 && make install
|
||||
|
||||
# Build latest Verilator
|
||||
WORKDIR /build
|
||||
RUN git clone --depth 1 https://github.com/verilator/verilator.git
|
||||
WORKDIR /build/verilator
|
||||
RUN autoconf && ./configure --prefix=/usr && make -j2 && make install
|
||||
|
||||
# Environment
|
||||
ENV GIT_SSL_NO_VERIFY=1
|
||||
ENV GUILE_LOAD_PATH=/server/embddr-scheme-library
|
||||
ENV LANG=en_US.UTF-8
|
||||
ENV DONOTUSEFIREJAIL=1
|
||||
|
||||
# Copy server files
|
||||
WORKDIR /server
|
||||
COPY server/* ./
|
||||
RUN git clone https://git.embddr.com/np/embddr-scheme-library.git
|
||||
RUN mkdir play-work /verilog-playground-store
|
||||
|
||||
EXPOSE 8080
|
||||
VOLUME /verilog-playground-store
|
||||
|
||||
CMD [ "guile", "-e", "main", \
|
||||
"./playground-server.scm", \
|
||||
"--port=8080", \
|
||||
"--addr=0.0.0.0", \
|
||||
"--host=https://play.embddr.com", \
|
||||
"--iverilog-wrap=./restrict", \
|
||||
"--vvp-wrap=./restrict", \
|
||||
"--verilator-sim-wrap=./restrict", \
|
||||
"--verilator-build-jobs=2", \
|
||||
"--max-len=10000", \
|
||||
"--work-base=play-work", \
|
||||
"--stor-base=/verilog-playground-store", \
|
||||
"--log-level=2" ]
|
||||
3
_web_server/container-create.sh
Executable file
3
_web_server/container-create.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
docker build -t playground .
|
||||
45
_web_server/container-run.sh
Executable file
45
_web_server/container-run.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ 0 -eq "$#" ]; then
|
||||
echo "Usage $0 start/stop/status"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
action=$1
|
||||
container=playground
|
||||
|
||||
case $action in
|
||||
"start")
|
||||
cid=$(docker container ls -q -a -f name=$container)
|
||||
|
||||
if [ -z "$cid" ]; then
|
||||
docker run -d --name $container \
|
||||
-p 127.0.0.1:8089:8080 \
|
||||
-v /srv/playground/storage:/verilog-playground-store \
|
||||
--cpus=1 --memory=16g --memory-swap=16g \
|
||||
$container
|
||||
else
|
||||
docker start $container
|
||||
fi
|
||||
;;
|
||||
|
||||
"stop")
|
||||
# Press twice CRTL-C
|
||||
docker kill -s SIGINT $container
|
||||
docker kill -s SIGINT $container
|
||||
;;
|
||||
|
||||
"status")
|
||||
cid=$(docker container ls -q -f name=$container)
|
||||
|
||||
if [ -z "$cid" ]; then
|
||||
echo "Stopped"
|
||||
else
|
||||
echo "Started"
|
||||
fi
|
||||
;;
|
||||
|
||||
"*")
|
||||
echo "Unknown action $action. Actions start, stop and status are allowed"
|
||||
;;
|
||||
esac
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
exe=$(basename $0)
|
||||
|
||||
exec firejail --noprofile --quiet \
|
||||
--rlimit-cpu=1 \
|
||||
--rlimit-as=100m \
|
||||
--rlimit-fsize=1m \
|
||||
"$exe" "$@"
|
||||
@@ -1,106 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Verilog Playground</title>
|
||||
<style type="text/css" media="screen">
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap');
|
||||
|
||||
.container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 5px;
|
||||
box-sizing: border-box;
|
||||
background: white;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
float: left;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
padding: 4px;
|
||||
border: 1px solid #008899;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ctrl-panel { width: 10%; }
|
||||
.editor { width: 45%; }
|
||||
.log-panel { width: 45%; }
|
||||
|
||||
#log {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0px;
|
||||
font-size: inherit;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="panel ctrl-panel">
|
||||
<button style="width:100%" onclick="save_code()">Save (ctrl+s)</button>
|
||||
<button style="width:100%" onclick="send_to_icarus()">Run Icarus Verilog</button>
|
||||
</div>
|
||||
<div class="panel editor" id="editor">@CODE@</div>
|
||||
<div class="panel log-panel">
|
||||
<textarea id="log" readonly="readonly"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.13.1/ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.13.1/ext-language_tools.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
var editor = ace.edit('editor');
|
||||
editor.setTheme('ace/theme/chrome');
|
||||
editor.session.setMode('ace/mode/verilog');
|
||||
editor.setOptions({
|
||||
tabSize : 2,
|
||||
fontSize : 14,
|
||||
fontFamily : 'JetBrains Mono, monospace',
|
||||
enableBasicAutocompletion : true,
|
||||
enableLiveAutocompletion : true
|
||||
});
|
||||
const log_area = document.getElementById('log');
|
||||
function send_to_icarus() {
|
||||
fetch('%IVERILOGPOSTURI%',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'text/plain',
|
||||
'Content-Type': 'text/plain'
|
||||
},
|
||||
body: editor.getValue()
|
||||
})
|
||||
.then((response) => response.text())
|
||||
.then((text) => { log_area.value = text; });
|
||||
};
|
||||
|
||||
function save_code() {
|
||||
fetch('%SAVECODEURI%',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'text/plain',
|
||||
'Content-Type': 'text/plain'
|
||||
},
|
||||
body: editor.getValue()
|
||||
})
|
||||
.then((response) => response.text())
|
||||
.then((text) => { window.location.href = text; });
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key.toLowerCase() === 's' && e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
save_code();
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1 +0,0 @@
|
||||
./firejailed.sh
|
||||
@@ -1,491 +0,0 @@
|
||||
#!/usr/bin/env -S guile -e "main" -s
|
||||
!#
|
||||
|
||||
;; -*- geiser-scheme-implementation: guile -*-
|
||||
|
||||
(import (srfi srfi-1)
|
||||
(srfi srfi-11)
|
||||
(srfi srfi-26)
|
||||
(srfi srfi-28)
|
||||
(rnrs bytevectors)
|
||||
(web server)
|
||||
(web request)
|
||||
(web response)
|
||||
(web uri)
|
||||
(sxml simple)
|
||||
(ice-9 regex)
|
||||
(ice-9 binary-ports)
|
||||
(ice-9 textual-ports)
|
||||
(ice-9 popen))
|
||||
|
||||
(import (embddr common)
|
||||
(embddr optargs))
|
||||
|
||||
(define INDEX-FILE "index.html")
|
||||
(define LOGGING #t)
|
||||
(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 (multistring . strings)
|
||||
(apply string-append
|
||||
(insert-between strings "\n")))
|
||||
|
||||
(define (not-found request)
|
||||
(values (build-response #:code 404)
|
||||
(string-append "Resource not found: "
|
||||
(uri->string (request-uri request)))))
|
||||
|
||||
(define* (make-response str #:key (type 'text/html))
|
||||
(values (build-response
|
||||
#:headers `((content-type . (,type (charset . "utf-8"))))
|
||||
#:code 200)
|
||||
str))
|
||||
|
||||
(define* (file-reader file-name #:key (max-read-length 512))
|
||||
(lambda (port)
|
||||
(with-input-from-file file-name
|
||||
(lambda ()
|
||||
(let loop ()
|
||||
(let ((data (get-bytevector-n (current-input-port)
|
||||
max-read-length)))
|
||||
(when (not (eof-object? data))
|
||||
(put-bytevector port data)
|
||||
(loop))))))))
|
||||
|
||||
(define* (file-response file #:key (type 'text/html))
|
||||
(make-response (file-reader file) #:type type))
|
||||
|
||||
(define-syntax guard
|
||||
(syntax-rules ()
|
||||
((_ default code...)
|
||||
(with-exception-handler (lambda (e) default)
|
||||
(lambda () code...)
|
||||
#:unwind? #t))))
|
||||
|
||||
(define (print . args)
|
||||
(display (apply format args)))
|
||||
|
||||
(define (println . args)
|
||||
(display (apply format args))
|
||||
(newline))
|
||||
|
||||
(define (printlog . args)
|
||||
(when LOGGING
|
||||
(display (apply format args))
|
||||
(newline)))
|
||||
|
||||
;;;
|
||||
;;; Return directory list
|
||||
;;;
|
||||
(define (list-dir path)
|
||||
(if (file-exists? path)
|
||||
(let ((dir (opendir path)))
|
||||
(let loop ((ls '()))
|
||||
(let ((item (readdir dir)))
|
||||
(if (eof-object? item)
|
||||
(begin
|
||||
(closedir dir)
|
||||
ls)
|
||||
(if (or (string=? item ".")
|
||||
(string=? item ".."))
|
||||
(loop ls)
|
||||
(loop (cons (string-append path "/" item) ls)))))))
|
||||
'()))
|
||||
|
||||
;;;
|
||||
;;; Recursive delete directory
|
||||
;;;
|
||||
(define (delete-recursive path)
|
||||
(let ((path (canonicalize-path path)))
|
||||
(if (eq? 'directory (stat:type (stat path)))
|
||||
(begin
|
||||
(for-each delete-recursive (list-dir path))
|
||||
(rmdir path))
|
||||
(delete-file path))))
|
||||
|
||||
;;;
|
||||
;;; Trim list
|
||||
;;;
|
||||
(define (list-trim l pred)
|
||||
(cond
|
||||
((null? l) '())
|
||||
((pred (car l)) (list-trim (cdr l) pred))
|
||||
(else
|
||||
(let ((lr (reverse l)))
|
||||
(if (pred (car lr))
|
||||
(reverse (list-trim (cdr lr) pred))
|
||||
l)))))
|
||||
|
||||
;;;
|
||||
;;; Read template to string
|
||||
;;;
|
||||
(define (read-template-text file subst)
|
||||
(let ((lines (read-template file "%~a%" subst)))
|
||||
(apply string-append
|
||||
(append-map (cut list <> "\n") lines))))
|
||||
|
||||
;;;
|
||||
;;; Execute system command and capture stdout and stderr to string list
|
||||
;;;
|
||||
(define* (system-to-string cmd #:key (pwd #f))
|
||||
(let* ((cmd (string-append cmd " 2>&1"))
|
||||
(cmd (if pwd (format "cd ~a; ~a" pwd cmd) cmd))
|
||||
(p (open-input-pipe cmd))
|
||||
(out (get-string-all p)))
|
||||
(values (close-pipe p) out)))
|
||||
|
||||
;;;
|
||||
;;; Unused
|
||||
;;;
|
||||
;; (define* (system-to-string-list cmd #:key (pwd #f))
|
||||
;; (let-values (((status out)
|
||||
;; (system-to-string cmd #:pwd pwd)))
|
||||
;; (values
|
||||
;; status
|
||||
;; (list-trim (string-split out #\newline) string-null?))))
|
||||
|
||||
;;;
|
||||
;;; Make pretty log from executable output
|
||||
;;;
|
||||
(define (exe-log-pretty cmdline status out)
|
||||
(string-append
|
||||
(format "$ ~a\n" cmdline)
|
||||
(format "Return code: ~a\n" status)
|
||||
(if (string-null? out)
|
||||
"\n"
|
||||
(format "--\n~a\n" out))))
|
||||
|
||||
;;;
|
||||
;;; Trivial sanitize verilog code
|
||||
;;;
|
||||
(define (sanitize-verilog code)
|
||||
(let* (;; $f* functions but not $finish
|
||||
(code (regexp-substitute/global #f "\\$f[a-hj-z][a-z]+" code 'pre "$error" 'post))
|
||||
;; $scanf
|
||||
(code (regexp-substitute/global #f "\\$.?scanf" code 'pre "$error" 'post))
|
||||
;; $readmem
|
||||
(code (regexp-substitute/global #f "\\$readmem[bh]" code 'pre "$error" 'post))
|
||||
;; $dump*
|
||||
(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))))))
|
||||
|
||||
;;;
|
||||
;;; Create dump module
|
||||
;;;
|
||||
(define (create-dump-module path modname top)
|
||||
(define (* . fmt) (display (apply format fmt)) (newline))
|
||||
(let ((filename (format "~a/~a.v" path 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
|
||||
;;; Returns work directory path string
|
||||
;;;
|
||||
(define (make-sim-workdir simulator code top)
|
||||
(let* ((work-dir (mkdtemp (format "work-~a-XXXXXX" (current-time))))
|
||||
(verilog-file (format "~a/testbench.sv" work-dir))
|
||||
(command-file (format "~a/testbench.vc" work-dir))
|
||||
(dump-file (create-dump-module work-dir "dump" top)))
|
||||
(with-output-to-file verilog-file (cut display code))
|
||||
(with-output-to-file command-file
|
||||
(lambda ()
|
||||
(cond
|
||||
((eq? simulator 'iverilog)
|
||||
(println "~a" verilog-file)
|
||||
(println "~a" dump-file)
|
||||
(println "+define+TESTBENCH")
|
||||
(println "+timescale+1ps/1ps")))))
|
||||
work-dir))
|
||||
|
||||
;;;
|
||||
;;; Compile sources and execute simulation with Icarus Verilog
|
||||
;;; Returns (values status log)
|
||||
;;;
|
||||
(define (exec-sim-iverilog work-dir vvp-exe iverilog-exe)
|
||||
;; Compile
|
||||
(let ((cmdline (format "~a -g2012 -o ~a/testbench.vvp -c~a/testbench.vc"
|
||||
iverilog-exe work-dir work-dir)))
|
||||
(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 (format "~a ~a/testbench.vvp" vvp-exe work-dir)))
|
||||
(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
|
||||
#: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)))
|
||||
(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 path from URI
|
||||
;;;
|
||||
(define (get-storage-path uri root-path)
|
||||
(string-trim
|
||||
(substring (uri-path uri)
|
||||
(string-length root-path))
|
||||
#\/))
|
||||
|
||||
;;;
|
||||
;;; Check storage path validity
|
||||
;;;
|
||||
(define (valid-storage-path path)
|
||||
(if (or (< (string-length path) 1)
|
||||
(> (string-length path) 32))
|
||||
#f
|
||||
(string-fold
|
||||
(lambda (c valid)
|
||||
(if (or (char-alphabetic? c)
|
||||
(char-numeric? c)
|
||||
(char=? c #\-))
|
||||
valid #f))
|
||||
#t path)))
|
||||
|
||||
;;;
|
||||
;;; Check storage exists
|
||||
;;;
|
||||
(define (storage-exists path)
|
||||
(let ((dir-stat (stat path #f))
|
||||
(file-stat (stat (format "~a/code.v" path) #f)))
|
||||
(and dir-stat file-stat
|
||||
(eq? (stat:type dir-stat) 'directory)
|
||||
(eq? (stat:type file-stat) 'regular)
|
||||
(not (zero? (logand #o444 (stat:perms file-stat))))
|
||||
(not (zero? (logand #o222 (stat:perms file-stat)))))))
|
||||
|
||||
;;;
|
||||
;;; Save code to storage
|
||||
;;;
|
||||
(define (save-to-storage path code)
|
||||
(with-output-to-file (format "~a/code.v" path)
|
||||
(cut display code)))
|
||||
|
||||
;;;
|
||||
;;; Read from storage
|
||||
;;;
|
||||
(define (read-from-storage path)
|
||||
(call-with-input-file (format "~a/code.v" path)
|
||||
get-string-all))
|
||||
|
||||
;;;
|
||||
;;; Web page handler
|
||||
;;;
|
||||
(define (make-page-handler host root index-file
|
||||
max-code-size
|
||||
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 '("iverilog")))
|
||||
(savecode-path (append root-path '("save")))
|
||||
(iverilog-post-uri (string-append host "/" (encode-and-join-uri-path iverilog-path)))
|
||||
(savecode-post-uri (string-append host "/" (encode-and-join-uri-path savecode-path)))
|
||||
(index-html (read-template-text index-file `(("IVERILOGPOSTURI" ,iverilog-post-uri)
|
||||
("SAVECODEURI" ,savecode-post-uri)))))
|
||||
|
||||
(lambda (request request-body)
|
||||
(let* ((path (split-and-decode-uri-path
|
||||
(uri-path
|
||||
(request-uri request))))
|
||||
(ref (assoc 'referer (request-headers request)))
|
||||
(ref-stor (if ref (get-storage-path (cdr ref) root) ""))
|
||||
(ref-stor (if (and (valid-storage-path ref-stor)
|
||||
(storage-exists ref-stor))
|
||||
ref-stor #f))
|
||||
(code (if request-body (utf8->string request-body) ""))
|
||||
(code (if (or (zero? max-code-size)
|
||||
(<= (string-length code) max-code-size))
|
||||
code
|
||||
(substring code 0 max-code-size))))
|
||||
|
||||
(printlog "-- path: ~a" path)
|
||||
(printlog "-- ref-stor: ~a" ref-stor)
|
||||
(printlog "-- length: ~a/~a"
|
||||
(request-content-length request)
|
||||
(string-length code))
|
||||
|
||||
(cond
|
||||
;; Iverilog
|
||||
((equal? path iverilog-path)
|
||||
(printlog "-- Request simulate")
|
||||
|
||||
(when ref-stor
|
||||
(save-to-storage ref-stor code))
|
||||
|
||||
(make-response
|
||||
(exec-sim 'iverilog
|
||||
(sanitize-verilog code)
|
||||
#:vvp-exe vvp-exe
|
||||
#:iverilog-exe iverilog-exe)
|
||||
#:type 'text/plain))
|
||||
|
||||
;; Save code
|
||||
((equal? path savecode-path)
|
||||
(printlog "-- Request save code")
|
||||
(let ((storage (or ref-stor
|
||||
(mkdtemp (format "~a-XXXXXX" (current-time))))))
|
||||
(save-to-storage storage code)
|
||||
(make-response
|
||||
(encode-and-join-uri-path
|
||||
(append root-path `(,storage))))))
|
||||
|
||||
;; Index page
|
||||
((equal? path root-path)
|
||||
(printlog "-- Request index page")
|
||||
(make-response
|
||||
(substitute index-html "@~a@" `((CODE ,DEFAULT-CODE)))))
|
||||
|
||||
;; Storage
|
||||
((and (> (length path)
|
||||
(length root-path))
|
||||
(every equal? path root-path))
|
||||
(let ((storage (if (null? path) "" (last path))))
|
||||
(printlog "-- Request code from storage ~a" storage)
|
||||
(if (and (valid-storage-path storage)
|
||||
(storage-exists storage))
|
||||
(let ((code (read-from-storage storage)))
|
||||
(make-response
|
||||
(substitute index-html "@~a@" `((CODE ,code)))))
|
||||
(make-response
|
||||
(substitute index-html "@~a@" `((CODE ,DEFAULT-CODE)))))))
|
||||
|
||||
;; Wrong request
|
||||
(else
|
||||
(printlog "-- Request wrong path")
|
||||
(not-found request)))))))
|
||||
|
||||
;;;
|
||||
;;; ----------------------------------------------------------------------
|
||||
;;; ------------------------------- MAIN ---------------------------------
|
||||
;;; ----------------------------------------------------------------------
|
||||
;;;
|
||||
|
||||
(define (print-help app-name)
|
||||
(define (-> . args)
|
||||
(display (apply format args) (current-error-port))
|
||||
(newline (current-error-port)))
|
||||
(let ((app-name (basename app-name)))
|
||||
(with-output-to-port (current-error-port)
|
||||
(lambda ()
|
||||
(-> "Usage: ~a [OPTION]..." app-name)
|
||||
(-> "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)")
|
||||
(-> " -h, --help Print this message and exit")
|
||||
(-> "")
|
||||
(-> "Source code and issue tracker: <https://github.com/punzik/>")))))
|
||||
|
||||
(define (main args)
|
||||
(debug-disable 'backtrace)
|
||||
(let-values
|
||||
(((opts rest err)
|
||||
(parse-opts (cdr args)
|
||||
'(("addr" #\a) required)
|
||||
'(("port" #\p) required)
|
||||
'(("host" #\s) required)
|
||||
'(("root" #\r) required)
|
||||
'(("vvp-exe") required)
|
||||
'(("iverilog-exe") required)
|
||||
'(("max-len") required)
|
||||
'(("help" #\h) none))))
|
||||
|
||||
(let ((addr (string-trim (or (option-get opts "addr") "127.0.0.1")))
|
||||
(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")))
|
||||
(max-code-size (string->number (string-trim (or (option-get opts "max-len") "0")))))
|
||||
|
||||
(cond
|
||||
(err
|
||||
(display (format "Unknown option '~a'\n" err))
|
||||
(print-help (car args))
|
||||
(exit -1))
|
||||
|
||||
((option-get opts "help")
|
||||
(print-help (car args))
|
||||
(exit -1))
|
||||
|
||||
(else
|
||||
(printlog "Listen on '~a' port '~a'" addr port)
|
||||
(printlog "Server URL: '~a/~a'" host root)
|
||||
(printlog "Max code size: ~a" max-code-size)
|
||||
|
||||
(run-server
|
||||
(make-page-handler host root INDEX-FILE max-code-size vvp iverilog)
|
||||
'http `(#:host ,addr #:port ,port)))))))
|
||||
BIN
_web_server/server/favicon.png
Normal file
BIN
_web_server/server/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
197
_web_server/server/index.html
Normal file
197
_web_server/server/index.html
Normal file
@@ -0,0 +1,197 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>Verilog Playground</title>
|
||||
<style type="text/css" media="screen">
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap');
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-variant-ligatures: none;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 3px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
#buttons {
|
||||
color: white;
|
||||
background-color: #2d3d40;
|
||||
width: 100%;
|
||||
padding: 3px;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
#logdiv, #editor, #buttons {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #505050;
|
||||
}
|
||||
|
||||
button {
|
||||
color: black;
|
||||
background-color: #dbdbdb;
|
||||
margin-left: 1px;
|
||||
margin-right: 1px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
button span.text { padding: 4px; }
|
||||
|
||||
#editor {
|
||||
height: 75vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#logdiv {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#logdiv {
|
||||
position: relative;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: none;
|
||||
stroke: white;
|
||||
stroke-width: 0;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
svg text {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 10pt;
|
||||
fill: white;
|
||||
}
|
||||
|
||||
svg #wave-signals {
|
||||
stroke: #00fcff;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
svg #wave-clock {
|
||||
stroke: #fffe9a;
|
||||
font-size: 8pt;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
svg #wave-signals text { font-size: 8pt; }
|
||||
svg #wave-clock text { font-size: 8pt; }
|
||||
|
||||
svg #wave-delim {
|
||||
stroke: #d0d0d0;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
svg #wave-background { fill: #1e2426; }
|
||||
|
||||
@media (orientation: landscape) and (not (pointer: coarse)) {
|
||||
body {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#text { flex-grow: 1; }
|
||||
|
||||
#editor {
|
||||
float: left;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#logdiv {
|
||||
float: left;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#logdiv pre {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="buttons">
|
||||
<button onclick="show_help()"><span class="text">?</span></button>
|
||||
<button onclick="save_code('%SAVECODEURI%')"><span class="text">Save</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>
|
||||
</div>
|
||||
|
||||
<div id="text">
|
||||
<div id="editor">@CODE@</div>
|
||||
<div id="logdiv"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.13.1/ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.13.1/ext-language_tools.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
var editor = ace.edit('editor');
|
||||
editor.setTheme('ace/theme/chrome');
|
||||
editor.session.setMode('ace/mode/verilog');
|
||||
editor.setOptions({
|
||||
tabSize : 2,
|
||||
fontSize : 14,
|
||||
fontFamily : 'JetBrains Mono, monospace',
|
||||
enableBasicAutocompletion : true,
|
||||
enableLiveAutocompletion : true
|
||||
});
|
||||
const log_area = document.getElementById('logdiv');
|
||||
function send_to_sim(uri) {
|
||||
query = uri + "?width=" + log_area.clientWidth;
|
||||
log_area.innerHTML = "Please wait...";
|
||||
fetch(query,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'text/plain',
|
||||
'Content-Type': 'text/plain'
|
||||
},
|
||||
body: editor.getValue()
|
||||
})
|
||||
.then((response) => response.text())
|
||||
.then((text) => { log_area.innerHTML = text; });
|
||||
};
|
||||
|
||||
function save_code(uri) {
|
||||
fetch(uri,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'text/plain',
|
||||
'Content-Type': 'text/plain'
|
||||
},
|
||||
body: editor.getValue()
|
||||
})
|
||||
.then((response) => response.text())
|
||||
.then((text) => { window.location.href = text; });
|
||||
};
|
||||
|
||||
function show_help() {
|
||||
alert("%HELPSTRING%");
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1101
_web_server/server/playground-server.scm
Executable file
1101
_web_server/server/playground-server.scm
Executable file
File diff suppressed because it is too large
Load Diff
12
_web_server/server/restrict
Executable file
12
_web_server/server/restrict
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ -z "$DONOTUSEFIREJAIL" ]; then
|
||||
exec firejail \
|
||||
--quiet --noprofile \
|
||||
--rlimit-cpu=5 \
|
||||
--rlimit-as=250m \
|
||||
--rlimit-fsize=250k \
|
||||
"$@"
|
||||
else
|
||||
exec timeout -v -s KILL 5 "$@"
|
||||
fi
|
||||
11
_web_server/server/top_iverilog.sv
Normal file
11
_web_server/server/top_iverilog.sv
Normal file
@@ -0,0 +1,11 @@
|
||||
`timescale 1ps/1ps
|
||||
|
||||
module __@TOPMODULE@__;
|
||||
@TOPMODULE@ @TOPMODULE@ ();
|
||||
|
||||
initial begin
|
||||
$dumpfile("@WORKDIR@/@TOPMODULE@.vcd");
|
||||
$dumpvars(1, @TOPMODULE@);
|
||||
$dumplimit(100000);
|
||||
end
|
||||
endmodule
|
||||
43
_web_server/server/top_verilator.cpp
Normal file
43
_web_server/server/top_verilator.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "verilated.h"
|
||||
#include "verilated_vcd_c.h"
|
||||
#include "V@TOPMODULE@.h"
|
||||
|
||||
#define DUMPFILE "@WORKDIR@/@TOPMODULE@.vcd"
|
||||
|
||||
int main(int argc, char** argv, char**) {
|
||||
// Setup context, defaults, and parse command line
|
||||
Verilated::debug(0);
|
||||
const std::unique_ptr<VerilatedContext> contextp{new VerilatedContext};
|
||||
contextp->traceEverOn(true);
|
||||
contextp->commandArgs(argc, argv);
|
||||
|
||||
// Construct the Verilated model, from Vtop.h generated from Verilating
|
||||
const std::unique_ptr<V@TOPMODULE@> topp{new V@TOPMODULE@{contextp.get()}};
|
||||
|
||||
VerilatedVcdC *vcd = new VerilatedVcdC;
|
||||
topp->trace(vcd, 99);
|
||||
vcd->open(DUMPFILE);
|
||||
|
||||
// Simulate until $finish
|
||||
while (!contextp->gotFinish()) {
|
||||
// Evaluate model
|
||||
topp->eval();
|
||||
vcd->dump(contextp->time());
|
||||
// Advance time
|
||||
if (!topp->eventsPending()) break;
|
||||
contextp->time(topp->nextTimeSlot());
|
||||
}
|
||||
|
||||
if (!contextp->gotFinish()) {
|
||||
VL_DEBUG_IF(VL_PRINTF("+ Exiting without $finish; no events left\n"););
|
||||
}
|
||||
|
||||
// Execute 'final' processes
|
||||
topp->final();
|
||||
|
||||
// Print statistical summary report
|
||||
contextp->statsPrintSummary();
|
||||
vcd->close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
./firejailed.sh
|
||||
Reference in New Issue
Block a user