Compare commits

..

29 Commits

Author SHA1 Message Date
Nikolay Puzanov
3aee2d2f38 Fix help message 2024-04-14 16:16:45 +03:00
Nikolay Puzanov
b84ec9a1c5 Remove clock from testbench input 2024-04-14 16:08:20 +03:00
Nikolay Puzanov
1e0bfb58cf Add support for verilator 5.018+ 2023-11-07 14:27:08 +03:00
Nikolay Puzanov
e67558de14 Enable dump in iverilog testbench template 2023-06-02 11:08:51 +03:00
Nikolay Puzanov
54f7e2be54 Enable trace of structs 2022-12-13 11:49:15 +03:00
Nikolay Puzanov
cc605cce85 Top module for all simulators must have an input clock signal 2022-12-09 10:21:40 +03:00
Nikolay Puzanov
2f9f5b6dd1 Disable Save hotkey (to prevent thoughtless use) 2022-12-09 10:13:03 +03:00
Nikolay Puzanov
cb8bc37dfe Disable ligatures 2022-12-09 09:23:07 +03:00
Nikolay Puzanov
1e12d5d3e2 Don't save unmodified code 2022-12-09 09:20:53 +03:00
Nikolay Puzanov
1bc6ec544d Save code only by Save button. Save always to new location 2022-12-09 09:07:13 +03:00
Nikolay Puzanov
d04282e1c7 Add simple scroll of long waveform 2022-12-08 19:06:08 +03:00
Nikolay Puzanov
664b7b1f5c Buttons can move to new line 2022-12-07 18:07:00 +03:00
Nikolay Puzanov
37b7a54d3b Visual improvements 2022-12-07 17:55:21 +03:00
Nikolay Puzanov
01c982fada Fix parsing canvas width 2022-12-07 17:54:50 +03:00
Nikolay Puzanov
931f4d8aa4 Fix signals sorting (put clock on the top) 2022-12-07 17:53:50 +03:00
Nikolay Puzanov
90905544d4 Upcase signal values 2022-12-07 17:53:17 +03:00
Nikolay Puzanov
ab5db7f6e8 Add waveforms 2022-12-07 17:00:51 +03:00
Nikolay Puzanov
e8ba09ecab Add VCD to SVG conversion functions 2022-12-06 20:06:26 +03:00
Nikolay Puzanov
daa744f8b2 Replace 'when not' to 'unless' 2022-12-05 20:26:04 +03:00
Nikolay Puzanov
c91003e34b Replace string-trim (left only trim) by string-trim-both 2022-12-04 19:53:56 +03:00
Nikolay Puzanov
53412381fb Optionally enable timestamp in save snippet uri 2022-12-04 12:09:32 +03:00
Nikolay Puzanov
536bde92df Allow to disable code sanitization 2022-12-04 11:29:25 +03:00
Nikolay Puzanov
b9cd29ad87 Fix Crtl-S hotkey 2022-12-04 11:29:10 +03:00
Nikolay Puzanov
c6c6744b22 Show execution time of compilation and simulation 2022-12-04 10:20:53 +03:00
Nikolay Puzanov
42f22147ea Limit dump file size for Icarus Verilog 2022-12-03 23:53:03 +03:00
Nikolay Puzanov
b4507004b8 Add latest Icarus Verilog and Verilator to Docker image 2022-12-03 19:31:39 +03:00
Nikolay Puzanov
74e76d8131 Add verilator build jobs option 2022-12-03 19:30:58 +03:00
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
9 changed files with 753 additions and 222 deletions

View File

@@ -10,4 +10,11 @@ module testbench;
initial begin
$finish;
end
`ifdef DUMP
initial begin
$dumpfile("testbench.fst");
$dumpvars(0, testbench);
end
`endif
endmodule

View File

@@ -3,10 +3,24 @@ 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 iverilog guile-3.0 locales git
&& 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
@@ -28,8 +42,10 @@ CMD [ "guile", "-e", "main", \
"--port=8080", \
"--addr=0.0.0.0", \
"--host=https://play.embddr.com", \
"--iverilog-exe=./iverilog", \
"--vvp-exe=./vvp", \
"--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", \

View File

@@ -11,19 +11,25 @@
box-sizing: border-box;
margin: 0;
padding: 0;
font-variant-ligatures: none;
}
body {
padding: 3px;
font-size: 14px;
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%;
height: 38px;
padding: 4px;
padding: 3px;
font-size: inherit;
}
@@ -38,16 +44,13 @@
background-color: #dbdbdb;
margin-left: 1px;
margin-right: 1px;
height: 100%;
margin-top: 2px;
margin-bottom: 2px;
height: 25px;
}
button span.text { padding: 4px; }
#text {
font-family: 'JetBrains Mono', monospace;
font-size: inherit;
}
#editor {
height: 75vh;
width: 100%;
@@ -63,6 +66,40 @@
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;
@@ -84,7 +121,7 @@
height: 100%;
}
#log {
#logdiv pre {
position: absolute;
height: 100%;
}
@@ -95,20 +132,18 @@
<body>
<div id="buttons">
<button onclick="show_help()"><span class="text">?</span></button>
<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>
<button onclick="save_code('%SAVECODEURI%')"><span class="text">Save</span></button>
Sim:
<button onclick="send_to_icarus()"><span class="text">Icarus</span></button>
<!-- button><span class="text">Verilator</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>
</div>
<div id="text">
<div id="editor">@CODE@</div>
<div id="logdiv">
<pre id="log"></pre>
</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">
@@ -122,10 +157,11 @@
enableBasicAutocompletion : true,
enableLiveAutocompletion : true
});
const log_area = document.getElementById('log');
function send_to_icarus() {
const log_area = document.getElementById('logdiv');
function send_to_sim(uri) {
query = uri + "?width=" + log_area.clientWidth;
log_area.innerHTML = "Please wait...";
fetch('%IVERILOGPOSTURI%',
fetch(query,
{
method: 'POST',
headers: {
@@ -156,13 +192,6 @@
alert("%HELPSTRING%");
};
document.addEventListener('keydown', (e) => {
if (e.key.toLowerCase() === 's' && e.ctrlKey) {
e.preventDefault();
save_code();
}
});
</script>
</body>
</html>

View File

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

View File

@@ -19,29 +19,25 @@
(ice-9 popen))
(import (embddr common)
(embddr optargs))
(embddr optargs)
(embddr vcd))
(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 USE-TIME-IN-SAVE-URL #f)
(define IVERILOG-EXE "iverilog")
(define VVP-EXE "vvp")
(define VERILATOR-EXE "verilator")
(define URI-IVERILOG "iverilog")
(define URI-VERILATOR "verilator")
(define URI-SAVE-CODE "save")
(define URI-SAVEAS-CODE "saveas")
(define LOG-DBG 3)
(define LOG-VERBOSE 2)
@@ -50,6 +46,23 @@
(define LOG-LEVEL LOG-VERBOSE)
(define DEFAULT-CODE
(string-append
"`timescale 1ps/1ps\n\n"
;; (format "module ~a (input clock);\n" TOP-MODULE)
(format "module ~a;\n" TOP-MODULE)
" logic clock = 1'b0;\n"
" initial forever #(5ns) clock = ~clock;\n"
"\n"
" initial begin\n"
" $display(\"Hello world!\");\n"
" repeat(10) @(posedge clock);\n"
" $finish();\n"
" end\n"
"endmodule\n"))
(define DEFAULT-CANVAS-WIDTH 800)
(define (multistring . strings)
(apply string-append
(insert-between strings "\n")))
@@ -72,7 +85,7 @@
;;; Logger
;;;
(define (logger . args)
(when (not (null? args))
(unless (null? args)
(let ((prefix
(format "~a | "
(strftime "%c" (localtime (current-time))))))
@@ -173,7 +186,7 @@
(when (or (not max-file-size)
(< readed max-file-size))
(let ((data (get-bytevector-n in max-read-length)))
(when (not (eof-object? data))
(unless (eof-object? data)
(put-bytevector port data)
(loop (+ readed (bytevector-length data))))))))
#:binary #t))))
@@ -190,6 +203,246 @@
#:content-type content-type
#:content-type-params content-type-params))
;;;
;;; Simple format with convert input number to inexact numbers
;;;
(define (format-inex . args)
(apply format
(map (lambda (arg)
(if (number? arg)
(exact->inexact
(/ (round (* arg 100)) 100))
arg))
args)))
;;;
;;; Make SVG drawing of VCD signals
;;;
(define* (vcd-signal->svg signal tstart tend x y width height text-position
#:key (id #f) (data-hw 3))
(let* ((time-per-pixel (/ (- tend tstart) width))
(sig-width (vcd-signal-size signal))
(sig-type (vcd-signal-type signal))
(y0 y)
(y1 (+ y height))
(yz (+ y (/ height 2)))
(half-dy (/ (- y1 y0) 2))
(id (if id (format " id=\"~a\"" id) "")))
(let next-sample ((samples (vcd-signal-get signal))
(value (if (eq? sig-type 'real)
0
(make-string sig-width #\x)))
(time tstart)
(svg '()))
(if (null? samples)
svg
(let ((sample-time (car (car samples)))
(sample-value (cdr (car samples))))
(if (and (< sample-time tend)
(or (< (- sample-time time) time-per-pixel)
(and (equal? value sample-value)
(not (eq? sig-type 'event)))))
(next-sample
(cdr samples)
(if (<= sample-time tstart)
sample-value
value)
time svg)
(next-sample
(cdr samples) sample-value sample-time
(cons
(cond
((or (eq? sig-type 'bits)
(eq? sig-type 'real))
(if (and (= sig-width 1)
(not (eq? sig-type 'real))
(not (char-ci=? (string-ref value 0) #\x)))
;; Scalar
(let ((x0 (+ x (/ (- time tstart) time-per-pixel)))
(x1 (+ x (/ (- sample-time tstart) time-per-pixel))))
(string-append
(format-inex "<path~a d=\"M~a ~a v~a\"/>" id x0 y0 (- y1 y0))
(format-inex "<path~a d=\"M~a ~a h~a\"/>"
id x0
(cond
((equal? value "0") y1)
((equal? value "1") y0)
(else yz))
(- x1 x0))))
;; Vector or Real
(let ((x0 (+ x (/ (- time tstart) time-per-pixel)))
(x1 (+ x (/ (- sample-time tstart) time-per-pixel))))
(string-append
;; Horizontal lines
(let ((x0 (+ x0 (if (<= time tstart) 0 data-hw)))
(x1 (- x1 (if (>= sample-time tend) 0 data-hw))))
(format-inex
"<path~a d=\"M~a ~a L~a ~a M~a ~a L~a ~a\"/>"
id x0 y0 x1 y0 x0 y1 x1 y1))
;; Left cross
(if (<= time tstart)
""
(format-inex "<path~a d=\"M~a ~a l~a ~a\"/><path d=\"M~a ~a l~a ~a\"/>"
id
x0 yz data-hw (- half-dy)
x0 yz data-hw (+ half-dy)))
;; Right cross
(if (>= sample-time tend)
""
(format-inex "<path~a d=\"M~a ~a l~a ~a\"/><path d=\"M~a ~a l~a ~a\"/>"
id
x1 yz (- data-hw) (- half-dy)
x1 yz (- data-hw) (+ half-dy)))
;; Text
(format-inex "<svg~a x=\"~a\" y=\"~a\" width=\"~a\" height=\"~a\">"
id
(+ x0 data-hw)
y0
(- x1 x0 (* data-hw 2))
(- y1 y0))
(format-inex "<text x=\"~a\" y=\"~a\">" 0 text-position)
(string-upcase
(if (or (eq? sig-type 'real)
(<= sig-width 4))
value
(vcd-binary->hex value #t)))
"</text></svg>"))))
;; Event TODO
((eq? sig-type 'event)
""))
svg))))))))
;;;
;;; Make legend SVG text for VCD
;;;
(define* (vcd-signals->legend signals text-spacing text-position)
(if (null? signals)
""
(let ((common-scope-len
(length
(fold
(lambda (scope common)
(let loop ((scope scope)
(common common)
(out '()))
(if (or (null? scope)
(null? common))
(reverse out)
(if (string-ci= (car scope)
(car common))
(loop (cdr scope)
(cdr common)
(cons (car scope) out))
(reverse out)))))
(vcd-signal-scope (car signals))
(map vcd-signal-scope (cdr signals))))))
(map
(lambda (sig n)
(string-append
(format-inex "<text x=\"0\" y=\"~a\">~a</text>"
(+ (* n text-spacing) text-position)
(string-concatenate
(insert-between
(append
(drop (vcd-signal-scope sig)
common-scope-len)
`(,(vcd-signal-name sig)))
".")))))
signals
(iota (length signals))))))
;;;
;;; Create SVG from VCD
;;;
(define* (vcd->svg vcd width #:key
(signal-height 15)
(signal-text-position 12)
(margin 5)
(signal-spacing 5)
(legend-width 150)
(extra-delim-y 3))
(let ((tstart (apply min (vcd-timestamps vcd)))
(tend (apply max (vcd-timestamps vcd)))
(signals (sort
(vcd-signals vcd)
(lambda (a b)
(and (not (equal? (vcd-signal-name b) "clock"))
(or
(and (equal? (vcd-signal-name a) "clock")
(not (equal? (vcd-signal-name b) "clock")))
(< (length (vcd-signal-scope a))
(length (vcd-signal-scope b)))
(string-ci<? (vcd-signal-name a)
(vcd-signal-name b))))))))
(if (<= tend tstart)
'()
(let ((signals-x (+ (* 2 margin) legend-width))
(signals-w (- width legend-width (* 3 margin)))
(height
(+ (* 2 margin)
(* signal-height (length signals))
(* signal-spacing (- (length signals) 1)))))
(append
;; Header
`(,(format-inex "<svg id=\"wave\" width=\"~a\" height=\"~a\"" width height)
,(format "preserveAspectRatio=\"xMidYMin slice\" role=\"img\">")
,(format "<g id=\"wave-background\"><rect width=\"100%\" height=\"100%\"/></g>"))
;; Legend
`(,(format-inex "<svg id=\"wave-legend\" x=\"~a\" y=\"~a\" width=\"~a\" height=\"~a\">"
margin margin legend-width (- height (* 2 margin))))
(vcd-signals->legend signals
(+ signal-height signal-spacing)
signal-text-position)
'("</svg>")
;; Clock
`(,(format "<g id=\"wave-clock\">"))
(vcd-signal->svg (car signals) tstart tend
signals-x margin
signals-w signal-height signal-text-position)
'("</g>")
;; Rest
`(,(format "<g id=\"wave-signals\">"))
(fold
(lambda (sig n out)
(append
out
(vcd-signal->svg sig tstart tend
signals-x
(+ margin
(* n signal-height)
(* n signal-spacing))
signals-w signal-height signal-text-position)))
'()
(cdr signals)
(iota (length
(cdr signals))
1))
'("</g>")
;; Delimiter
`(,(format-inex "<g id=\"wave-delim\"><path d=\"M~a ~a v~a\"/></g>"
(+ legend-width (* 2 margin)) (- margin extra-delim-y)
(- height (* 2 (- margin extra-delim-y)))))
;; Close svg tag
'("</svg>"))))))
;;;
;;; Execute system command and capture stdout and stderr to string
;;;
@@ -200,6 +453,22 @@
(out (get-string-all p)))
(values (close-pipe p) out)))
;;;
;;; Same as system-to-string but returns execution time (in ms) also
;;;
(define* (system-to-string-with-time cmd #:key (pwd #f))
(let ((start-time (gettimeofday)))
(let-values
(((status out)
(system-to-string cmd #:pwd pwd)))
(let ((stop-time (gettimeofday)))
(values status out
(exact->inexact
(- (+ (* (car stop-time) 1000)
(/ (cdr stop-time) 1000))
(+ (* (car start-time) 1000)
(/ (cdr start-time) 1000)))))))))
;;;
;;; Execute system command and capture stdout and stderr to string list
;;;
@@ -213,10 +482,10 @@
;;;
;;; Make pretty log from executable output
;;;
(define (exe-log-pretty cmdline status out)
(define (exe-log-pretty cmdline status out time)
(string-append
(format "$ ~a\n" cmdline)
(format "Return code: ~a\n" status)
(format "Return code: ~a, Exec time: ~a ms\n" status time)
(if (string-null? out)
"\n"
(format "--\n~a\n" out))))
@@ -235,21 +504,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,116 +521,213 @@
(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" metatop-file)
(println "~a" verilog-file)
(println "~a" dump-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 jobs 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 "-DTESTBENCH")
(println "--timescale 1ps/1ps")
(println "--top-module ~a" top)
(println "--Mdir ~a" (path+ work-dir top))
(println "-cc")
(println "-O2")
(when (> jobs 0)
(println "--build-jobs ~a" jobs))
(println "-o ~a" top)
(println "--exe")
(println "--build")
(println "-sv")
(println "-Wno-WIDTH")
(println "+1800-2023ext+sv")
(println "--timing")
(println "--trace")
(println "--trace-structs")
(println "--trace-depth 1")
(println "--quiet-exit")
(println "~a" verilog-file)
(println "~a.cpp" top))))
work-dir))
;;;
;;; Execute secuence of commands and return (values status "execution log")
;;; Break execution on error
;;;
(define (exec-sequence cmds)
(let-values
(((status logs)
(let next-cmd ((cmds cmds)
(logs '()))
(if (null? cmds)
(values 0 logs)
(let ((cmd (car cmds)))
(let-values (((status out time)
(system-to-string-with-time cmd)))
(let ((logs (cons (exe-log-pretty cmd status out time) logs)))
(if (zero? status)
(next-cmd (cdr cmds) logs)
(values status logs)))))))))
(values status (string-concatenate (reverse logs)))))
;;;
;;; Read and parse VCD file
;;;
(define* (vcd-file-read file #:optional (signal-need? (lambda (s) #t)))
(if (file-exists? file)
(guard #f
(call-with-input-file file
(cut vcd-parse <> signal-need?)))
#f))
;;;
;;; Compile sources and execute simulation with Icarus Verilog
;;; Returns (values status log)
;;; Returns (values status log vcd)
;;;
(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 -o ~a -c~a" iverilog-exe exe-file 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)
(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)))
(vcd-file (path+ work-dir (format "~a.vcd" top)))
(cmds `(,(format "~a -g2012 -s __~a__ -o ~a -c~a"
(wrap-exe IVERILOG-EXE iverilog-wrap)
top exe-file command-file)
,(format "~a -N ~a" (wrap-exe VVP-EXE vvp-wrap) exe-file))))
;; Execute
(let ((cmdline (format "~a -N ~a" vvp-exe 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)))))))))))
(let-values (((status log)
(exec-sequence cmds)))
(if (zero? status)
(values status log (vcd-file-read
vcd-file
(lambda (sig)
(>= (length (vcd-signal-scope sig)) 2))))
(values status log #f)))))
;;;
;;; Get iverilog version
;;; Compile sources and execute simulation with Verilator
;;; Returns (values status log vcd)
;;;
(define (iverilog-version iverilog-exe)
(define (exec-sim-verilator top work-dir verilator-wrap verilator-sim-wrap)
(let* ((command-file (path+ work-dir (format "~a.vc" top)))
(vcd-file (path+ work-dir (format "~a.vcd" top)))
(cmds `(,(format "~a -f ~a"
(wrap-exe VERILATOR-EXE verilator-wrap)
command-file)
,(wrap-exe (path+ work-dir (format "~a/~a" top top))
verilator-sim-wrap))))
(let-values (((status log)
(exec-sequence cmds)))
(if (zero? status)
(values status log (vcd-file-read
vcd-file
(lambda (sig)
(>= (length (vcd-signal-scope sig)) 2))))
(values status log #f)))))
;;;
;;; Execute simulation
;;;
(define* (exec-sim simulator code base top #:key
(vvp-wrap "") (iverilog-wrap "") (metatop "")
(verilator-wrap "") (verilator-sim-wrap "")
(verilator-cpp "") (verilator-build-jobs 0))
(let-values
(((work-dir status log vcd)
(cond
;; Run Icarus Verilog
((eq? simulator 'iverilog)
(let ((work-dir (make-iverilog-workdir code metatop base top)))
(let-values (((status log vcd)
(exec-sim-iverilog top work-dir iverilog-wrap vvp-wrap)))
(values work-dir status log vcd))))
;; Run Verilator
((eq? simulator 'verilator)
(let ((work-dir (make-verilator-workdir code verilator-cpp verilator-build-jobs base top)))
(let-values (((status log vcd)
(exec-sim-verilator top work-dir verilator-wrap verilator-sim-wrap)))
(values work-dir status log vcd))))
;; Inknown simulator
(else
(values #f #f #f #f)))))
(if (not work-dir)
(values ("ERROR: Unknown simulator") #f)
(begin
;; Delete work dir
(when DELETE-WORK-DIR
(delete-recursive work-dir))
;; Return (values log vcd)
(values
(string-append
log
(format "-----------------\nSimulation complete~a\n"
(if (zero? status) " succesfully"" with errors")))
vcd)))))
;;;
;;; Get app version
;;;
(define* (app-version exe #:optional (option "--version"))
(let-values (((status out)
(system-to-string-list
(format "~a -V" iverilog-exe))))
(format "~a ~a" exe option))))
(if (and (zero? status)
(not (null? out)))
(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
;;;
(define (get-storage-dir uri root-path)
(string-trim
(string-trim-both
(substring (uri-path uri)
(string-length root-path))
#\/))
@@ -422,44 +773,67 @@
(call-with-input-file (path+ path SNIPPET-FILE)
get-string-all))
;;;
;;; Make log HTML
;;;
(define* (make-log-html log vcd canvas-width #:key
(minimum-sample-width 10)
(maximum-canvas-width 20000))
(if vcd
(let ((need-width
(* minimum-sample-width
(length (vcd-timestamps vcd)))))
(format "~a<br/>\n<pre id=\"log\">~a</pre>\n"
(string-concatenate
(vcd->svg vcd
(if (< need-width canvas-width)
canvas-width
(if (> need-width maximum-canvas-width)
maximum-canvas-width
need-width))))
log))
(format "<pre>~a</pre>\n" log)))
;;;
;;; Web page handler
;;;
(define (make-page-handler host root index-file
work-base stor-base
max-code-size
vvp-exe iverilog-exe)
max-code-size sanitize
iverilog-wrap vvp-wrap
verilator-wrap verilator-sim-wrap verilator-build-jobs)
(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))
("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 VERILATOR-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."
"3. Code size should not exceed 10000 characters."
"4. Code execution time no longer than 5 seconds.")
"\\n")))))))
"1. The top module must be named 'testbench'."
"2. Code size should not exceed 10000 characters."
"3. 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)))
(lambda (request request-body)
(let (;; Requested resource path
@@ -484,9 +858,17 @@
(<= (string-length code) max-code-size))
code
(substring code 0 max-code-size)))
"")))
""))
;; Request query
(query (let ((q (uri-query (request-uri request))))
(if q
(map (lambda (qstr) (string-split q #\=))
(string-split q #\;))
'()))))
(logger LOG-VERBOSE "Request ~a:~a" (request-method request) path)
(logger LOG-VERBOSE "Request query:~a" query)
(logger LOG-DBG " stor:'~a' len:~a/~a"
ref-stor-dir
(request-content-length request)
@@ -538,42 +920,64 @@
;;
((eq? 'POST (request-method request))
(cond
;; Run iverilog simulation
((equal? path iverilog-path)
(logger LOG-DBG "Request iverilog simulation")
;; Run simulation
((or (equal? path iverilog-path)
(equal? path verilator-path))
(let ((simulator
(if (equal? path iverilog-path)
'iverilog
'verilator)))
(when ref-stor-dir
(save-to-storage (path+ stor-base ref-stor-dir) code))
(logger LOG-DBG "Request ~a simulation" (symbol->string simulator))
(let-values
(((log vcd)
(exec-sim simulator
(if sanitize (sanitize-verilog code) code)
work-base
TOP-MODULE
#:metatop iverilog-metatop
#:vvp-wrap vvp-wrap
#:iverilog-wrap iverilog-wrap
#:verilator-wrap verilator-wrap
#:verilator-sim-wrap verilator-sim-wrap
#:verilator-cpp verilator-cpp
#:verilator-build-jobs verilator-build-jobs)))
(make-response
(exec-sim 'iverilog
(sanitize-verilog code)
work-base
#:vvp-exe vvp-exe
#:iverilog-exe iverilog-exe)
#:content-type 'text/plain))
(let ((canvas-width
(let ((v (assoc "width" query)))
(or (and v (string->number (cadr v)))
DEFAULT-CANVAS-WIDTH))))
(make-response
(make-log-html log vcd canvas-width)
#:content-type 'text/plain)))))
;; Save snippet
((or (equal? path savecode-path)
(equal? path saveas-path))
(let ((saveas (equal? path saveas-path)))
(logger LOG-DBG "Request code saving~a"
(if saveas " as new snippet" ""))
(let ((stor-dir
(if (or saveas
(not ref-stor-dir))
((or (equal? path savecode-path))
(logger LOG-DBG "Request code saving")
(let ((old-code
(if ref-stor-dir
(read-from-storage (path+ stor-base ref-stor-dir))
DEFAULT-CODE)))
(if (equal? code old-code)
;; If code is not changed do nothing
(make-response
(encode-and-join-uri-path
(append root-path `(,ref-stor-dir)))
#:content-type 'text/plain)
;; New code save to new location
(let ((stor-dir
(basename
(mkdtemp
(path+
stor-base
(format "~a-XXXXXX"
(current-time)))))
ref-stor-dir)))
(save-to-storage (path+ stor-base stor-dir) code)
(make-response
(encode-and-join-uri-path
(append root-path `(,stor-dir)))
#:content-type 'text/plain))))
(if USE-TIME-IN-SAVE-URL
(format "~a-XXXXXX" (current-time))
"XXXXXX"))))))
(save-to-storage (path+ stor-base stor-dir) code)
(make-response
(encode-and-join-uri-path
(append root-path `(,stor-dir)))
#:content-type 'text/plain)))))
;; Wrong POST request
(else
@@ -604,20 +1008,29 @@
(-> "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.")
(-> " --verilator-build-jobs N Verilator parallel build.")
(-> " --max-len LEN Set maximum code size in symbols. Default: 0 (infinite)")
(-> " --dont-sanitize Do not sanitize verilog code (dangerous)")
(-> " --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-both str)
str))
(define (main args)
(debug-disable 'backtrace)
(let-values
@@ -627,24 +1040,32 @@
'(("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)
'(("verilator-build-jobs") required)
'(("max-len") required)
'(("dont-sanitize") none)
'(("work-base") required)
'(("stor-base") required)
'(("log-level") 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"))))
(work-base (string-trim (or (option-get opts "work-base") "./")))
(stor-base (string-trim (or (option-get opts "stor-base") "./")))
(log-level (string->number (string-trim (or (option-get opts "log-level") "1")))))
(let ((addr (string-trim-both (or (option-get opts "addr") "127.0.0.1")))
(port (string->number (string-trim-both (or (option-get opts "port") "8080"))))
(host (string-trim-both (or (option-get opts "host") "http://127.0.0.1:8080")))
(root (string-trim-both (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")))
(verilator-build-jobs (string->number (string-trim-both (or (option-get opts "verilator-build-jobs") "0"))))
(max-code-size (string->number (string-trim-both (or (option-get opts "max-len") "0"))))
(sanitize (not (option-get opts "dont-sanitize")))
(work-base (string-trim-both (or (option-get opts "work-base") "./")))
(stor-base (string-trim-both (or (option-get opts "stor-base") "./")))
(log-level (string->number (string-trim-both (or (option-get opts "log-level") "1")))))
(cond
(err
@@ -660,7 +1081,13 @@
(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 "verilator build jobs: ~a" verilator-build-jobs)
(logger LOG-INFO "Max code size: ~a" max-code-size)
(logger LOG-INFO "Sanitize code: ~a" sanitize)
(logger LOG-INFO "Work base path: '~a'" work-base)
(logger LOG-INFO "Storage base path: '~a'" stor-base)
(logger LOG-INFO "Log level: '~a'" log-level)
@@ -668,5 +1095,7 @@
(run-server
(make-page-handler host root INDEX-FILE
work-base stor-base
max-code-size vvp iverilog)
max-code-size sanitize
iverilog-wrap vvp-wrap
verilator-wrap verilator-sim-wrap verilator-build-jobs)
'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@__;
@TOPMODULE@ @TOPMODULE@ ();
initial begin
$dumpfile("@WORKDIR@/@TOPMODULE@.vcd");
$dumpvars(1, @TOPMODULE@);
$dumplimit(100000);
end
endmodule

View 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;
}

View File

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