Add sources

This commit is contained in:
Nikolay Puzanov 2023-06-11 16:15:40 +03:00
parent 82f90610fb
commit 686d12bf81
48 changed files with 23261 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
results.txt

45
run.sh Executable file
View File

@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -e
BUILD=__build.sh
RUN=__run.sh
if [ -n "$1" ]
then
tests=$1
else
tests=$(ls -1d test-*)
fi
echo >> results.txt
echo "---------- Simulator's benchmark -----------" >> results.txt
echo $(date) >> results.txt
echo >> results.txt
for test_dir in $tests
do
if [ ! -d "$test_dir" ]
then
echo "Directory $test_dit is not exists. Break"
exit -1
fi
if [ -e $test_dir/$BUILD -a -e $test_dir/$RUN ]
then
echo "#### Run benchmark in $test_dir"
cd $test_dir
./$BUILD
start_ms=$(date +%s%N | cut -b1-13)
./$RUN
stop_ms=$(date +%s%N | cut -b1-13)
cd ..
ms=$(expr $stop_ms - $start_ms)
echo "#### $test_dir: $ms milliseconds"
echo
echo "$test_dir: $ms" >> results.txt
else
echo "Skip $test_dir directory"
fi
done

29
scripts/bin2initial.scm Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env guile
!#
;; -*- geiser-scheme-implementation: guile -*-
(use-modules
(ice-9 binary-ports)
(ice-9 format)
(srfi srfi-11)
(rnrs bytevectors))
(define (print-verilog-header binary-file reg-name)
(format #t "initial begin\n")
(let ((words (call-with-input-file binary-file
(lambda (port)
(bytevector->uint-list
(get-bytevector-all port) 'little 4)))))
(for-each
(lambda (x n)
(format #t " ~a[~a] = 32'h~8,'0x;\n" reg-name n x))
words (iota (length words))))
(format #t "end\n"))
(let ((args (command-line)))
(if (not (= (length args) 3))
(format #t "Usage: ~a <BINARY_FILE_NAME> <RAM_REG_NAME>\n" (car args))
(let ((file-name (cadr args))
(reg-name (caddr args)))
(print-verilog-header file-name reg-name))))

28
scripts/bin2mem.scm Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env guile
!#
;; -*- geiser-scheme-implementation: guile -*-
(use-modules
(ice-9 binary-ports)
(ice-9 format)
(srfi srfi-11)
(rnrs bytevectors))
(define (print-verilog-header binary-file mem-size)
(let ((words (call-with-input-file binary-file
(lambda (port)
(bytevector->uint-list
(get-bytevector-all port) 'little 4)))))
(for-each
(lambda (x)
(format #t "~8,'0x\n" x))
(append words
(make-list (- mem-size (length words)) 0)))))
(let ((args (command-line)))
(if (not (= (length args) 3))
(format #t "Usage: ~a <BINARY_FILE_NAME> <MEM_SIZE_KB>\n" (car args))
(let ((file-name (cadr args))
(mem-size (floor (/ (string->number (caddr args)) 4))))
(print-verilog-header file-name mem-size))))

244
scripts/common.scm Normal file
View File

@ -0,0 +1,244 @@
(cond-expand
(guile
(define-module (common))
(import (srfi srfi-1)
(srfi srfi-26)
(srfi srfi-28)
(srfi srfi-60)
(ice-9 textual-ports))
(export log2 clog2 round-to
one? power-of-two?
transpose
number->string-binary
number->string-binary-slice
number->string-hex
number->bits
string-c-radix->number
has-duplicates? find-duplicates
insert-between
string-replace-text
string-split-str
string-split-trim
get-word
substitute
read-template
make-mux-selectors))
(else
(error "Guile is required")))
;;; Log2
(define (log2 x)
(/ (log x) (log 2)))
;;; Ceiling of log2 ($clog2 function in SV)
(define (clog2 x)
(inexact->exact (ceiling (log2 x))))
;;; Check a number is a power of two
(define (power-of-two? x)
(let ((l (round (log2 x))))
(= (expt 2 l) x)))
;;; Check for (x == 1)
(define (one? x) (= x 1))
;;; Round to the 'n' decimal place
(define (round-to n num)
(let ((k (expt 10 n)))
(/ (round (* num k)) k)))
;;; Transpose of matrix (list of lists)
(define (transpose m)
(apply map (cons list m)))
;;; Convert number to binary string of length 'len'
(define (number->string-binary n len)
(list->string
(reverse
(map (lambda (x) (if x #\1 #\0))
(list-tabulate len (lambda (i) (bit-set? i n)))))))
;;; Convert number to binary and slice from msb to lsb
(define (number->string-binary-slice n msb lsb)
(list->string
(reverse
(drop
(map (lambda (x) (if x #\1 #\0))
(list-tabulate (+ msb 1) (lambda (i) (bit-set? i n))))
lsb))))
;;; Convert number to hex with length l (padded left with 0)
(define (number->string-hex n l)
(let* ((s (number->string n 16))
(sl (string-length s)))
(if (<= l sl)
s
(string-append (make-string (- l sl) #\0) s))))
;;; Convert number to bit list
(define (number->bits x len)
(map (lambda (n) (if (bit-set? n x) 1 0)) (iota len)))
;;; Convert arbitrary radix string in C-format (0x, 0b 0..) to number
(define (string-c-radix->number str)
(if (and str (string? str))
(let ((str (string-trim-both str)))
(cond
((string-every #\0 str) 0)
((string-prefix? "0x" str)
(string->number (substring str 2) 16))
((string-prefix? "0b" str)
(string->number (substring str 2) 2))
((string-prefix? "0" str)
(string->number (substring str 1) 8))
(else
(string->number str 10))))
#f))
;;; Check list for duplicates
(define (has-duplicates? items less)
(if (< (length items) 2)
#f
(let ((sorted (sort items less)))
(any (lambda (a b) (and (not (less a b))
(not (less b a))))
sorted
(append (cdr sorted)
`(,(car sorted)))))))
;;; Return first duplicated item or #f if no duplicates
(define (find-duplicates items less)
(if (null? items)
#f
(let ((sorted (sort items less)))
(let loop ((item (car sorted))
(rest (cdr sorted)))
(cond
((null? rest) #f)
((and (not (less item (car rest)))
(not (less (car rest) item))) item)
(else (loop (car rest) (cdr rest))))))))
;;; In the list b0 leaves only the last most significant (other than b1) bit
(define (bits-first-diff-msb b0 b1)
(let loop ((b0 (reverse b0))
(b1 (reverse b1))
(keep '()))
(if (null? b0)
keep
(let ((b0b (car b0))
(b0s (cdr b0)))
(if (= b0b (car b1))
(loop b0s (cdr b1) (cons #f keep))
(append (make-list (length b0s) #f) (cons b0b keep)))))))
;;; Return bit lists of address selectors
;;; If list item is #f then bit is not care
;;; First element of each list is a address
;;; Example:
;;; (make-mux-selectors '(#x10 #x20 #x30)) ->
;;; ((#x30 #f #f #f #f 1 1)
;;; (#x20 #f #f #f #f 0 1)
;;; (#x10 #f #f #f #f #f 0))
(define (make-mux-selectors addrs)
(let ((bit-width (apply max (map integer-length addrs)))
(addrs (sort addrs >)))
(map
(lambda (addr)
(let ((others (remove (cut = addr <>) addrs))
(abits (number->bits addr bit-width)))
(cons
addr
(apply map
(lambda bits
(let ((abit (car bits))
(obits (cdr bits)))
(if (every not obits) #f abit)))
(cons
abits
(map
(lambda (other)
(let ((obits (number->bits other bit-width)))
(bits-first-diff-msb abits obits)))
others))))))
addrs)))
;;; Insert object between list items
(define (insert-between lst x)
(if (or (null? lst)
(null? (cdr lst)))
lst
(cons* (car lst) x
(insert-between (cdr lst) x))))
;;; Racket-like string-replace
(define* (string-replace-text str from to #:key (all #t))
(let ((flen (string-length from))
(tlen (string-length to)))
(let replace ((str str) (idx 0))
(if (>= idx (string-length str))
str
(let ((occ (string-contains str from idx)))
(if occ
(let ((str (string-replace str to occ (+ occ flen))))
(if all
(replace str (+ occ tlen 1))
str))
str))))))
;;; Substitute template
(define (substitute text template-format subst-list)
(fold (lambda (s out)
(string-replace-text
out
(format template-format (first s))
(format "~a" (second s))))
text subst-list))
;;; Read template and substitute replacements
;;; Returns list of strings (lines)
(define (read-template template-file template-format subst-list)
(let ((ls (call-with-input-file template-file
(lambda (port)
(let loop ((l '()))
(let ((s (get-line port)))
(if (eof-object? s)
(reverse l)
(loop (cons s l)))))))))
(map (lambda (str)
(substitute str template-format subst-list))
ls)))
;;; Split the string STR into a list of the substrings delimited by DELIMITER
(define (string-split-str str delimiter)
(if (string-null? str)
'()
(let ((didx (string-contains str delimiter)))
(if didx
(cons (substring str 0 didx)
(string-split-str
(substring str (+ didx (string-length delimiter)))
delimiter))
(list str)))))
;;; Split string and remove empty itemes
(define (string-split-trim str pred?)
(remove string-null?
(string-split str pred?)))
;;; Get word delimited by pred? from port
(define* (get-word port #:optional (pred? char-whitespace?))
(let get-word-rec ((chlist '()))
(let ((c (get-char port)))
(if (eof-object? c)
(if (null? chlist)
#f
(list->string (reverse chlist)))
(if (pred? c)
(if (null? chlist)
(get-word-rec chlist)
(list->string (reverse chlist)))
(get-word-rec (cons c chlist)))))))

66
scripts/optargs.scm Normal file
View File

@ -0,0 +1,66 @@
(define-module (optargs))
(import (srfi srfi-1)
(srfi srfi-11)
(srfi srfi-37))
(export parse-opts
option-get)
;;;
;;; TODO: Write docs
;;;
(define (option-get opts name)
(let ((opt (assoc name opts)))
(if opt
(cdr opt)
#f)))
(define (option-set opts name value)
(if (assoc name opts)
(map (lambda (opt)
(if (equal? (car opt) name)
(cons name value)
opt))
opts)
(alist-cons name value opts)))
(define (option-add opts name value)
(if (assoc name opts)
(option-set opts name
(cons value
(option-get opts name)))
(alist-cons name `(,value) opts)))
;;; opt-spec - '(("option" #\o) [none | required | optional | multiple])
(define (parse-opts args . opt-spec)
(args-fold
;; args
args
;; options
(map (lambda (spec)
(let* ((names (list-ref spec 0))
(type (list-ref spec 1))
(name (car names))
(req? (eq? type 'required))
(opt? (eq? type 'optional))
(many? (eq? type 'multiple)))
(option names (or many? req?) opt?
(if many?
(lambda (opt nm arg opts rest error)
(values (if arg
(option-add opts name arg)
opts)
rest
error))
(lambda (opt nm arg opts rest error)
(values (option-set opts name (if arg arg #t)) rest error))))))
opt-spec)
;; unrecognized options
(lambda (opt name arg opts rest error)
(values opts rest name))
;; operands
(lambda (operand opts rest error)
(values opts (cons operand rest) error))
;; seeds
'() '() #f))

424
scripts/picorv32-bus-mux-gen.scm Executable file
View File

@ -0,0 +1,424 @@
#!/usr/bin/env -S guile -e "main" -s
!#
;; -*- geiser-scheme-implementation: guile -*-
(add-to-load-path (dirname (current-filename)))
(import
(srfi srfi-1) ; Lists
(srfi srfi-11) ; let-values
(srfi srfi-28) ; Simple format
(common)
(optargs))
;;; Default address width
(define ADDR_WIDTH 32)
;;; Default data width
(define DATA_WIDTH 32)
;;; Count of indentation spaces
(define INDENT 2)
;;; Convert arbitrary radix string in C-format to number
(define (string-c-radix->number str)
(if (and str (string? str))
(let ((str (string-trim-both str)))
(cond
((string-every #\0 str) 0)
((string-prefix? "0x" str)
(string->number (substring str 2) 16))
((string-prefix? "0b" str)
(string->number (substring str 2) 2))
((string-prefix? "0" str)
(string->number (substring str 1) 8))
(else
(string->number str 10))))
#f))
;;; Print to stderr
(define (warning . rest)
(display "Warning: " (current-error-port))
(display (apply format rest) (current-error-port))
(newline (current-error-port)))
(define (error . rest)
(display "Error: " (current-error-port))
(display (apply format rest) (current-error-port))
(newline (current-error-port)))
(define (error-and-exit . rest)
(apply error rest)
(exit EXIT_FAILURE))
;;; Println with indentationm
;;; (-> [indent] format-string [parameters])
(define (->> . fmt)
(cond
((null? fmt) #f)
((number? (car fmt))
(let ((indent (car fmt))
(rest (cdr fmt)))
(when (not (null? rest))
(display (list->string (make-list (* indent INDENT) #\space)))
(display (apply format rest)))))
(else
(display (apply format fmt)))))
(define (-> . fmt)
(apply ->> fmt)
(newline))
;;;
;;; ----------------------------------------------------------------------
;;; -------------------------- VERILOG BACKEND ---------------------------
;;; ----------------------------------------------------------------------
;;;
;;; Print module header
(define (print-verilog-module-header slaves module-name)
(let ((slaves-count (length slaves)))
(-> 0 "// This file is auto-generated. Do not edit")
(->)
(-> 0 "// Slaves address ranges:")
(for-each
(lambda (slave n)
(let ((b (car slave))
(s (cdr slave)))
(-> 0 "// ~a - 0x~a-0x~a"
n
(number->string-hex b (/ ADDR_WIDTH 4))
(number->string-hex (- (+ b s) 1) (/ ADDR_WIDTH 4)))))
slaves
(iota slaves-count))
(->)
(-> 0 "// i_slave_rdata bits:")
(for-each
(lambda (n)
(-> 0 "// ~a: i_slave_rdata[~a:~a]"
n
(- (* (+ n 1) DATA_WIDTH) 1)
(* n DATA_WIDTH)))
(iota slaves-count))
(->)
(-> 0 "module ~a" module-name)
(-> 1 "(input wire clock,")
(-> 1 " input wire reset,")
(->)
(-> 1 " // PicoRV32 memory interface")
(-> 1 " // Look-ahead address and multiplexed signals")
(-> 1 " // Some bits of address may not be used")
(-> 1 " /* verilator lint_off UNUSED */")
(-> 1 " input wire [~a:0] i_la_addr," (- ADDR_WIDTH 1))
(-> 1 " /* verilator lint_on UNUSED */")
(-> 1 " output wire [~a:0] o_rdata," (- DATA_WIDTH 1))
(-> 1 " input wire i_valid,")
(-> 1 " output wire o_ready,")
(->)
(-> 1 " // Slaves interface")
(-> 1 " input wire [~a:0] i_slave_rdata," (- (* slaves-count DATA_WIDTH) 1))
(-> 1 " output wire [~a:0] o_slave_valid," (- slaves-count 1))
(-> 1 " input wire [~a:0] i_slave_ready);" (- slaves-count 1))
(->)))
;;; Print module footer
(define (print-verilog-module-footer module-name)
(-> 0 "endmodule // ~a" module-name)
(-> 0 "`default_nettype wire"))
;;; Print selectors
(define (print-verilog-selectors slaves)
(let ((count (length slaves))
(addrs (map car slaves)))
(-> 1 "wire [~a:0] selector;" (- count 1))
(-> 1 "reg [~a:0] selector_reg;" (- count 1))
(->)
(-> 1 "always @(posedge clock)")
(-> 2 "if (reset)")
(-> 3 "selector_reg <= ~a'd0;" count)
(-> 2 "else")
(-> 3 "if (!i_valid)")
(-> 4 "selector_reg <= selector;")
(->)
(let ((selectors (make-mux-selectors addrs)))
(for-each
(lambda (addr n)
(let ((selector (cdr (assq addr selectors))))
(if (every not selector)
(-> 1 "assign selector[~a] = 1'b1;" n)
(begin
(-> 1 "assign selector[~a] =" n)
(let loop ((bits selector)
(n 0)
(need-and-sign #f))
(if (null? bits) #f
(begin
(let ((bit (car bits)))
(loop (cdr bits) (+ n 1)
(if bit
(begin
(when need-and-sign (-> " &&"))
(->> 2 "i_la_addr[~a] == 1'b~a" n bit)
#t)
need-and-sign))))))
(-> ";")))
(->)))
addrs (iota count)))))
;;; Print one range body
(define (print-verilog-body slaves)
(let ((slaves-count (length slaves)))
(-> 1 "assign o_slave_valid = selector_reg & {~a{i_valid}};" slaves-count)
(-> 1 "assign o_ready = |(i_slave_ready & selector_reg);")
(->)
(-> 1 "assign o_rdata =")
(for-each
(lambda (n)
(->> 2 "(i_slave_rdata[~a:~a] & {~a{selector_reg[~a]}})"
(- (* DATA_WIDTH (+ n 1)) 1)
(* DATA_WIDTH n)
DATA_WIDTH
n)
(-> "~a" (if (= n (- slaves-count 1)) ";" " |")))
(iota slaves-count))
(->)))
;;; Print formal
(define (print-verilog-formal slaves module-name)
(let ((slaves-count (length slaves)))
(-> 0 "`ifdef FORMAL")
(->)
(-> 1 "always @(*) begin : formal_selector")
(-> 2 "integer ones, n;")
(-> 2 "ones = 0;")
(->)
(-> 2 "// Check for selector is zero or one-hot value")
(-> 2 "for (n = 0; n < ~a; n = n + 1)" slaves-count)
(-> 3 "if (selector[n] == 1'b1)")
(-> 4 "ones = ones + 1;")
(->)
(-> 2 "assert(ones < 2);")
(->)
(-> 2 "// Check for correct address ranges decode")
(for-each
(lambda (slave n)
(let ((b (car slave))
(s (cdr slave)))
(-> 2 "if (i_la_addr >= ~a'h~a && i_la_addr <= ~a'h~a)"
ADDR_WIDTH (number->string b 16)
ADDR_WIDTH (number->string (- (+ b s) 1) 16))
(-> 3 "assert(selector[~a] == 1'b1);" n)))
slaves
(iota slaves-count))
(-> 1 "end")
(->)
(-> 1 "// Check multiplexer")
(-> 1 "always @(*) begin : formal_mux")
(-> 2 "case (selector_reg)")
(for-each
(lambda (n)
(-> 3 "~a'b~a: begin"
slaves-count
(list->string
(map (lambda (x) (if (= x n) #\1 #\0))
(reverse (iota slaves-count)))))
(-> 4 "assert(o_rdata == i_slave_rdata[~a:~a]);"
(- (* (+ n 1) DATA_WIDTH) 1)
(* n DATA_WIDTH))
(-> 4 "assert(o_ready == i_slave_ready[~a]);" n)
(-> 4 "assert(o_slave_valid[~a] == i_valid);" n)
(-> 3 "end")
)
(iota slaves-count))
(-> 3 "~a'b~a: begin" slaves-count (make-string slaves-count #\0))
(-> 4 "assert(o_rdata == ~a'd0);" DATA_WIDTH)
(-> 4 "assert(o_ready == 1'b0);")
(-> 4 "assert(o_slave_valid == ~a'd0);" slaves-count)
(-> 3 "end")
(-> 2 "endcase")
(-> 1 "end")
(->)
(-> 1 "// Assume module is not in reset state")
(-> 1 "always @(*) assume(reset == 1'b0);")
(->)
(-> 1 "// Make flag that the past is valid")
(-> 1 "reg have_past = 1'b0;")
(-> 1 "always @(posedge clock) have_past <= 1'b1;")
(->)
(-> 1 "// Check for selector_reg is valid and stable when i_valid is 1")
(-> 1 "always @(posedge clock) begin")
(-> 2 "if (have_past)")
(-> 3 "if (i_valid)")
(-> 4 "if ($rose(i_valid))")
(-> 5 "assert(selector_reg == $past(selector));")
(-> 4 "else")
(-> 5 "assert($stable(selector_reg));")
(-> 1 "end")
(->)
(-> 0 "`endif // FORMAL")
(->)))
;;; Print verilog code for slaves
(define (print-verilog slaves module-name)
(print-verilog-module-header slaves module-name)
(print-verilog-selectors slaves)
(print-verilog-body slaves)
(print-verilog-formal slaves module-name)
(print-verilog-module-footer module-name))
(define (print-sby-script module-name)
(-> "# To run formal verification call SymbiYosys:")
(-> "# $ sby -f ~a.sby" module-name)
(->)
(-> "[options]")
(-> "mode prove")
(->)
(-> "[engines]")
(-> "smtbmc boolector")
(->)
(-> "[script]")
(-> "read -vlog95 -formal ~a.v" module-name)
(-> "prep -top ~a" module-name)
(->)
(-> "[files]")
(-> "~a.v" module-name))
;;;
;;; Main
;;;
;;; Check for slave address ranges for intersection
(define (slaves-intersected? slaves)
(let ((sorted (sort slaves (lambda (a b) (< (car a) (car b))))))
(let check ((slave (car sorted))
(slaves (cdr sorted)))
(if (null? slaves)
#f
(let ((next (car slaves)))
(let ((b0 (car slave))
(s0 (cdr slave))
(b1 (car next)))
(if (> (+ b0 s0) b1)
#t
(check next (cdr slaves)))))))))
(define (print-help app-name)
(with-output-to-port (current-error-port)
(lambda ()
(-> "Usage: ~a [OPTION]... [FILE]" app-name)
(-> "Make verilog module of PicoRV bus multiplexer.")
(-> "Optional FILE - is an address spaces description file.")
(-> "")
(-> "Options:")
(-> " -s, --slave ADDRESS_RANGE Add slave address range")
(-> " -m, --module MODULE_NAME Verilog module name (optional)")
(-> " -f, --formal Print script (sby) for SymbiYosys")
(-> " -h, --help Print this message and exit")
(-> "")
(-> "Where ADDRESS_RANGE is string of BASE_ADDRESS+LENGTH")
(-> "")
(-> "Generate mux for two address ranges [0..0x0fff] and [0x1000..0x1fff]:")
(-> " ~a -s 0x0+0x1000 -s 0x1000+0x1000" app-name)
(-> "")
(-> "If FILE is specified --slave (-s) option will ignored.")
(-> "")
(-> "Source code and issue tracker: <https://github.com/punzik/>"))))
(define (main args)
(debug-disable 'backtrace)
(let-values
(((opts rest err)
(parse-opts (cdr args)
'(("slave" #\s) multiple)
'(("module" #\m) required)
'(("help" #\h) none)
'(("formal" #\f) none))))
(if err
(begin
(error "Unknown option\n")
(print-help (car args))
(exit -1))
(let ((slaves (option-get opts "slave"))
(mod-name (option-get opts "module"))
(help (option-get opts "help"))
(formal (option-get opts "formal"))
(file-name (if (null? rest) #f (car rest))))
(if (or help (not slaves))
(print-help (car args))
(let-values
(((slaves mod-name)
(if file-name
;; Read config from file
(let ((cfg (with-input-from-file file-name read)))
(values
(map (lambda (sl) (cons (car sl) (cadr sl)))
(filter list? cfg))
(if mod-name
mod-name
(find string? cfg))))
;; Use arguments
(values
(sort
(map (lambda (slave-opt)
(let ((base+size (string-split slave-opt #\+)))
(if (not (= (length base+size) 2))
(error-and-exit "Wrong slave format")
(let ((base (string-c-radix->number (first base+size)))
(size (string-c-radix->number (second base+size))))
(if (not (and base size))
(error-and-exit "Wrong address/size number format '~a+~a'" base size)
(cons base size))))))
slaves)
(lambda (a b) (< (car a) (car b))))
mod-name))))
(let ((module-name
(if mod-name
mod-name
(format "picorv32_busmux_1x~a" (length slaves)))))
;; Check slaves integrity
(cond
;; Address space size is zero
((any (lambda (slave) (zero? (cdr slave))) slaves)
(error-and-exit "Address space size is zero"))
;; Address space size is not power of two
((any (lambda (slave) (not (power-of-two? (cdr slave)))) slaves)
(error-and-exit "Address space size is not power of two"))
;; Base address is not divisible by address space size
((any (lambda (slave) (not
(zero?
(remainder (car slave)
(cdr slave)))))
slaves)
(error-and-exit "Base address is not divisible by address space size"))
;; Address range is not in range of 2^ADDR_WIDTH
((any (lambda (slave)
(> (+ (car slave) (cdr slave))
(expt 2 ADDR_WIDTH)))
slaves)
(error-and-exit "Slave address is out of ~a bit range" ADDR_WIDTH))
;; Address ranges intersected
((slaves-intersected? slaves)
(error-and-exit "Slave address ranges is intersected"))
;; All OK
(else
(if formal
(print-sby-script module-name)
(print-verilog slaves module-name)))))))))))

1346
scripts/register-gen.scm Executable file

File diff suppressed because it is too large Load Diff

15
source/bus_mux.sby Normal file
View File

@ -0,0 +1,15 @@
# To run formal verification call SymbiYosys:
# $ sby -f bus_mux.sby
[options]
mode prove
[engines]
smtbmc boolector
[script]
read -vlog95 -formal bus_mux.v
prep -top bus_mux
[files]
bus_mux.v

114
source/bus_mux.v Normal file
View File

@ -0,0 +1,114 @@
// This file is auto-generated. Do not edit
// Slaves address ranges:
// 0 - 0x00000000-0x0000ffff
// 1 - 0x01000000-0x01000fff
// i_slave_rdata bits:
// 0: i_slave_rdata[31:0]
// 1: i_slave_rdata[63:32]
module bus_mux
(input wire clock,
input wire reset,
// PicoRV32 memory interface
// Look-ahead address and multiplexed signals
// Some bits of address may not be used
/* verilator lint_off UNUSED */
input wire [31:0] i_la_addr,
/* verilator lint_on UNUSED */
output wire [31:0] o_rdata,
input wire i_valid,
output wire o_ready,
// Slaves interface
input wire [63:0] i_slave_rdata,
output wire [1:0] o_slave_valid,
input wire [1:0] i_slave_ready);
wire [1:0] selector;
reg [1:0] selector_reg;
always @(posedge clock)
if (reset)
selector_reg <= 2'd0;
else
if (!i_valid)
selector_reg <= selector;
assign selector[0] =
i_la_addr[24] == 1'b0;
assign selector[1] =
i_la_addr[24] == 1'b1;
assign o_slave_valid = selector_reg & {2{i_valid}};
assign o_ready = |(i_slave_ready & selector_reg);
assign o_rdata =
(i_slave_rdata[31:0] & {32{selector_reg[0]}}) |
(i_slave_rdata[63:32] & {32{selector_reg[1]}});
`ifdef FORMAL
always @(*) begin : formal_selector
integer ones, n;
ones = 0;
// Check for selector is zero or one-hot value
for (n = 0; n < 2; n = n + 1)
if (selector[n] == 1'b1)
ones = ones + 1;
assert(ones < 2);
// Check for correct address ranges decode
if (i_la_addr >= 32'h0 && i_la_addr <= 32'hffff)
assert(selector[0] == 1'b1);
if (i_la_addr >= 32'h1000000 && i_la_addr <= 32'h1000fff)
assert(selector[1] == 1'b1);
end
// Check multiplexer
always @(*) begin : formal_mux
case (selector_reg)
2'b01: begin
assert(o_rdata == i_slave_rdata[31:0]);
assert(o_ready == i_slave_ready[0]);
assert(o_slave_valid[0] == i_valid);
end
2'b10: begin
assert(o_rdata == i_slave_rdata[63:32]);
assert(o_ready == i_slave_ready[1]);
assert(o_slave_valid[1] == i_valid);
end
2'b00: begin
assert(o_rdata == 32'd0);
assert(o_ready == 1'b0);
assert(o_slave_valid == 2'd0);
end
endcase
end
// Assume module is not in reset state
always @(*) assume(reset == 1'b0);
// Make flag that the past is valid
reg have_past = 1'b0;
always @(posedge clock) have_past <= 1'b1;
// Check for selector_reg is valid and stable when i_valid is 1
always @(posedge clock) begin
if (have_past)
if (i_valid)
if ($rose(i_valid))
assert(selector_reg == $past(selector));
else
assert($stable(selector_reg));
end
`endif // FORMAL
endmodule // bus_mux
`default_nettype wire

View File

@ -0,0 +1,38 @@
BasedOnStyle: LLVM
IndentWidth: 4
UseTab: false
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AlignConsecutiveMacros: true
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments: true
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: WithoutElse
BinPackArguments: true
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterControlStatement: MultiLine
AfterFunction: true
AfterStruct: true
BeforeElse: true
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: false
IncludeBlocks: Regroup
SpaceBeforeParens: ControlStatements
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 4
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false

4
source/firmware/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.elf
*.bin
*.map
*.asm

44
source/firmware/Makefile Normal file
View File

@ -0,0 +1,44 @@
PROJECT := fw
SOURCES := crt0.s main.c uprintf.c
CPU_RAM_REG := ram_reg
ARCH := riscv32-none-elf
CFLAGS := -O2 -Wall -march=rv32i -mabi=ilp32 -mstrict-align \
-nostartfiles \
-ffunction-sections -lgcc \
-Wl,-Tpicorv32-minimal.ld,-static,-Map,$(PROJECT).map
# CFLAGS := -O3 -Wall -march=rv32i -mabi=ilp32 -mstrict-align \
# -nostartfiles \
# -ffunction-sections \
# -ffreestanding -lgcc \
# -Wl,-T,picorv32-minimal.ld,-static,-Map,$(PROJECT).map
# -nostdlib
ELF = $(PROJECT).elf
BIN = $(PROJECT).bin
ASM = $(PROJECT).asm
SVH = $(PROJECT).svh
MEM = $(PROJECT).mem
all: $(ELF) $(BIN) $(MEM) Makefile
$(ELF): $(SOURCES) picorv32-minimal.ld Makefile
$(ARCH)-gcc $(CFLAGS) -o $(ELF) $(SOURCES)
$(BIN): $(ELF)
$(ARCH)-objcopy -O binary $(ELF) $(BIN)
$(SVH): $(BIN)
../../scripts/bin2initial.scm $(BIN) $(CPU_RAM_REG) > $(SVH)
$(MEM): $(BIN)
../../scripts/bin2mem.scm $(BIN) 65536 > $(MEM)
disasm: $(ASM)
$(ASM): $(ELF)
$(ARCH)-objdump -d $(ELF) > $(ASM)
clean:
rm -f $(ELF) $(BIN) $(PROJECT).map $(SVH) $(MEM)

7
source/firmware/crt0.s Normal file
View File

@ -0,0 +1,7 @@
.section .init, "ax"
.global _start
_start:
la sp, __stack_top
add s0, sp, zero
jal zero, main
.end

16384
source/firmware/fw.mem Normal file

File diff suppressed because it is too large Load Diff

86
source/firmware/main.c Normal file
View File

@ -0,0 +1,86 @@
#include "../io_reg.h"
#include "uprintf.h"
#include <stdint.h>
void put_char(char c)
{
IO_REG_CONSOLE = c | IO_REG_CONSOLE_SEND;
}
#define N 50
#define CHUNK 4
#define ARR_LEN (10 * N / 3 + 1)
static int arr[ARR_LEN];
void print_digit(int d)
{
static int cnt = 0;
p("%d", d);
cnt++;
if (cnt == CHUNK) {
p("\n");
cnt = 0;
}
}
/* See: https://en.wikipedia.org/wiki/Spigot_algorithm */
int main()
{
p("\nComputation of %d first digits of PI\n", N);
for (int i = 0; i < ARR_LEN; i++)
arr[i] = 2;
int nines = 0;
int predigit = 0;
for (int j = 1; j < N + 1; j++) {
int q = 0;
for (int i = ARR_LEN; i > 0; i--) {
int x = 10 * arr[i - 1] + q * i;
arr[i - 1] = x % (2 * i - 1);
q = x / (2 * i - 1);
}
arr[0] = q % 10;
q = q / 10;
if (9 == q)
nines++;
else if (10 == q) {
print_digit(predigit + 1);
for (int k = 0; k < nines; k++)
print_digit(0);
predigit = 0;
nines = 0;
}
else {
print_digit(predigit);
predigit = q;
if (0 != nines) {
for (int k = 0; k < nines; k++)
print_digit(9);
nines = 0;
}
}
}
p("%d", predigit);
p("\nDONE\n");
/* Stop simulation */
IO_REG_CTRL = IO_REG_CTRL_STOP;
for (;;) {
};
}

View File

@ -0,0 +1,34 @@
MEMORY { ram (rx) : ORIGIN = 0, LENGTH = 65536 }
SECTIONS
{
. = 0x00;
.text : {
*(.init*)
*(.text*)
} > ram
.bss (NOLOAD) :
{
*(.bss*)
*(COMMON)
} > ram
.data :
{
*(.data*)
} > ram
.stack (NOLOAD) :
{
/*
. = ALIGN(4);
. = . + STACK_SIZE;
*/
. = ORIGIN(ram) + LENGTH(ram) - 4;
. = ALIGN(4);
__stack_top = .;
} > ram
}

16
source/firmware/shell.nix Normal file
View File

@ -0,0 +1,16 @@
{ nixpkgs ? import <nixpkgs> {} }:
let cross-rv5 = import <nixpkgs> {
crossSystem = {
config = "riscv32-none-elf";
gcc = { arch = "rv32i"; abi = "ilp32"; };
libc = "newlib";
};
};
in
cross-rv5.mkShell {
nativeBuildInputs = [ nixpkgs.gnumake nixpkgs.guile_3_0 ];
shellHook = ''
export NIX_SHELL_NAME="riscv"
'';
}

396
source/firmware/uprintf.c Normal file
View File

@ -0,0 +1,396 @@
#include "uprintf.h"
#include <stdarg.h>
#include <stdint.h>
/*
* ------------------- Простая замена printf ------------------------
*
* Спецификаторы в форматной строке так же как и в printf начинаются
* со знака %. После этого знака может идти спецификатор типа с
* опциональным указанием ширины выравнивания, символа для
* выравнивания и направления выравнивания.
*
* Первым должен идти спецификатор символа выравнивания - знак '
* (одинарная скобка). После него - символ для выравнивания.
*
* По умолчанию числа выравниваются символом '0' по правому краю, а
* строки пробелом по левому краю.
*
* Далее идет указатель ширины - десятичное число со знаком,
* обозначающее минимальную длину выводимой строки. Если строка
* получается меньше этого числа, недостающая длина добирается
* символами выравнивания. Если ширина указана в виде отрицательного
* числа, то выравнивание выполняется по правому краю. Если для числа
* не указан символ выравнивания, то оно всегда выравнивается символом
* '0' по правому краю. Вместо числа может стоять символ '*',
* говоряший о том, что ширину нужно брать из аргумента функции.
*
* Спецификатор типа может иметь префикс 'l', обозначающий длинное целое
* (64 бита) и/или префикс 'u', обозначающий беззнаковое число.
*
* Спецификаторы типа:
* d, i Десятичное целое.
* o Восьмиричное целое.
* b Двоичное целое.
* x Шестнадцатиричное целое (строчными).
* X Шестнадцатиричное целое (прописные).
* c Символ.
* s Строка. Если указана ширина, то выравнивание по левому краю.
*
* Пример:
* p("I:+%'=-6i+\n", 10);
* Вывод: I:+====10+
*
*/
static const char abet[2][16] = {{'0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'A', 'B', 'C', 'D', 'E', 'F'},
{'0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f'}};
typedef enum {
PS_REGULAR = 0,
PS_JSYM_SPEC,
PS_JSYM,
PS_WIDTH_SIGN,
PS_WIDTH_ARG,
PS_WIDTH,
PS_TYPE,
} pstate;
static int l_strlen(const char *str)
{
int l = 0;
while (*str++)
l++;
return l;
}
/* Helper functions for p() */
static void print_string(put_char_func pc, const char *str, int width,
char wchr)
{
int sl, w;
if (width < 0) {
sl = l_strlen(str);
for (w = -width; w > sl; w--)
pc(wchr);
}
for (sl = 0; *str; str++, sl++)
pc(*str);
if (width > 0) {
for (w = width; w > sl; w--)
pc(wchr);
}
}
/*
static void div(uint32_t n, uint32_t d, uint32_t *q, uint32_t *r)
{
uint32_t _q = 0;
uint32_t _r = 0;
uint32_t b = 0x80000000L;
if (d == 0) return;
while (b) {
_r <<= 1;
_r |= (n & b) ? 1 : 0;
if (_r >= d) {
_r -= d;
_q |= b;
}
b >>= 1;
}
*q = _q;
*r = _r;
}
*/
static void print_decimal(put_char_func pc, uint32_t u, int negative,
unsigned int base, int width, int lcase, char wchr)
{
if (base > 16) base = 16;
if (base < 2) base = 2;
if (lcase != 0) lcase = 1;
char s[66];
int si = 64;
uint32_t l;
s[si--] = 0;
do {
// div(u, base, &u, &l);
l = u % base;
u = u / base;
s[si--] = abet[lcase][l];
} while (u > 0);
if (negative) {
if (wchr == '0') {
pc('-');
if (width > 0)
width--;
else if (width < 0)
width++;
}
else
s[si--] = '-';
}
si++;
print_string(pc, s + si, width, wchr);
}
static void print_unsigned(put_char_func pc, uint32_t s, unsigned int base,
int width, int lcase, char wchr)
{
print_decimal(pc, s, 0, base, width, lcase, wchr);
}
static void print_signed(put_char_func pc, int64_t s, unsigned int base,
int width, int lcase, char wchr)
{
if (s < 0)
print_decimal(pc, (uint32_t)-s, 1, base, width, lcase, wchr);
else
print_decimal(pc, (uint32_t)s, 0, base, width, lcase, wchr);
}
static int l_isdigit(char c)
{
return (c >= '0' && c <= '9') ? 1 : 0;
}
/* Like a vprintf */
void pv(put_char_func pc, const char *fmt, va_list ap)
{
/* Initialization for supress gcc warnings */
int width = 0, wsign = 1, lng = 0, sgn = 0, ab = 0;
/* Width adjustment character */
char wchr = 0;
unsigned int base = 0;
char c;
int64_t d;
pstate st = PS_REGULAR;
for (;;) {
c = *fmt;
if (c == 0) break;
switch (st) {
/* ---------------------------------------------------------- */
case PS_REGULAR:
fmt++;
if (c == '%') {
st = PS_JSYM_SPEC;
lng = 0;
sgn = 1;
width = 0;
wsign = 1;
base = 10;
ab = 0;
wchr = 0;
}
else
pc(c);
break;
/* ---------------------------------------------------------- */
case PS_JSYM_SPEC:
if (c == '\'') {
fmt++;
st = PS_JSYM;
}
else
st = PS_WIDTH_SIGN;
break;
/* ---------------------------------------------------------- */
case PS_JSYM:
fmt++;
wchr = c;
st = PS_WIDTH_SIGN;
break;
/* ---------------------------------------------------------- */
case PS_WIDTH_SIGN:
if (c == '-') {
fmt++;
wsign = -1;
}
st = PS_WIDTH_ARG;
break;
/* ---------------------------------------------------------- */
case PS_WIDTH_ARG:
if (c == '*') {
fmt++;
width = va_arg(ap, int);
st = PS_TYPE;
}
else
st = PS_WIDTH;
break;
/* ---------------------------------------------------------- */
case PS_WIDTH:
if (l_isdigit(c)) {
fmt++;
width = width * 10 + (c - '0');
}
else
st = PS_TYPE;
break;
/* ---------------------------------------------------------- */
case PS_TYPE:
fmt++;
switch (c) {
case 'l':
lng = 1;
continue;
case 'u':
sgn = 0;
continue;
case 'd':
case 'i':
case 'b':
case 'o':
case 'x':
case 'X':
if ((lng) && (sgn))
d = (int64_t)va_arg(ap, long long);
else if ((lng) && (!sgn))
d = (int64_t)va_arg(ap, unsigned long long);
else if ((!lng) && (sgn))
d = (int64_t)va_arg(ap, int);
else
d = (int64_t)va_arg(ap, unsigned int);
ab = 0;
switch (c) {
case 'd':
case 'i':
base = 10;
break;
case 'b':
base = 2;
break;
case 'o':
base = 8;
break;
case 'x':
ab = 1;
case 'X':
base = 16;
break;
default:
break;
}
if (!wchr) {
wchr = '0';
wsign = -1;
}
if (sgn)
print_signed(pc, d, base, (wsign > 0) ? width : -width, ab,
wchr);
else
print_unsigned(pc, (uint32_t)d, base,
(wsign > 0) ? width : -width, ab, wchr);
break;
case 'c':
pc((char)va_arg(ap, int));
break;
case 's':
if (!wchr) wchr = ' ';
print_string(pc, va_arg(ap, char *),
(wsign > 0) ? width : -width, wchr);
break;
case '%':
pc('%');
break;
default:
pc('%');
pc(c);
}
st = PS_REGULAR;
break;
/* ---------------------------------------------------------- */
default:
st = PS_REGULAR;
}
}
}
/* Universal printf */
void pp(put_char_func pc, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
pv(pc, fmt, ap);
va_end(ap);
}
/* Print to console */
void p(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
pv(put_char, fmt, ap);
va_end(ap);
}
/* Print to string */
int psn(char *str, int size, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
int n = 0;
/* Nested function. GCC specific */
void put_char_str(char c)
{
if (n < size) {
*str++ = c;
n++;
}
}
pv(put_char_str, fmt, ap);
va_end(ap);
return n;
}

15
source/firmware/uprintf.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef _UPRINTF
#define _UPRINTF
#include <stdint.h>
#include <stdarg.h>
typedef void (*put_char_func)(char c);
extern void put_char(char c);
void pv(put_char_func pc, const char *fmt, va_list ap);
void pp(put_char_func pc, const char *fmt, ...);
void p(const char *fmt, ...);
int psn(char *str, int size, const char *fmt, ...);
#endif /* _UPRINTF */

8
source/generate.sh Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -e
../scripts/register-gen.scm io.reg > io_reg.v
../scripts/register-gen.scm -c io.reg > io_reg.h
../scripts/register-gen.scm -t io.reg > io_reg.txt
../scripts/picorv32-bus-mux-gen.scm -s 0+0x10000 -s 0x01000000+0x1000 -m bus_mux > bus_mux.v
../scripts/picorv32-bus-mux-gen.scm -s 0+0x10000 -s 0x01000000+0x1000 -m bus_mux -f > bus_mux.sby

16
source/io.reg Normal file
View File

@ -0,0 +1,16 @@
;; -*- lisp -*-
((base #x01000000)
(address-width 32)
(data-width 32)
byte-enable
(reg "ctrl"
(info "Control register")
(bits 1 "stop" w (reset #b0)))
(reg "console" read-notify
(info "Virtual console port")
(bits 8 "data" rw (info "Read/write char from/to console"))
(bits 1 "send" hs (info "Write 1 to send symbol"))
(bits 1 "valid" r (info "Symbol in DATA is valid"))))

17
source/io_reg.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef _IO_REG_H_
#define _IO_REG_H_
#define IO_REG_BASE 0x1000000
/* -- Register 'CTRL' -- */
#define IO_REG_CTRL (*(volatile uint32_t*)(IO_REG_BASE + 0x00000000))
#define IO_REG_CTRL_STOP (1 << 0)
/* -- Register 'CONSOLE' -- */
#define IO_REG_CONSOLE (*(volatile uint32_t*)(IO_REG_BASE + 0x00000004))
#define IO_REG_CONSOLE_DATA__MASK 0x000000ff
#define IO_REG_CONSOLE_DATA__SHIFT 0
#define IO_REG_CONSOLE_SEND (1 << 8)
#define IO_REG_CONSOLE_VALID (1 << 9)
#endif // _IO_REG_H_

30
source/io_reg.txt Normal file
View File

@ -0,0 +1,30 @@
Register map of IO_REG (base: 0x1000000)
========================================
| Offset | Name | Description |
|------------+---------+----------------------|
| 0x00000000 | CTRL | Control register |
| 0x00000004 | CONSOLE | Virtual console port |
CTRL Register (0x00000000)
--------------------------
Control register
| Bits | Name | Mode | Reset | Description |
|------+------+------+-------+-------------|
| 0 | STOP | WO | 0 | |
CONSOLE Register (0x00000004)
-----------------------------
Virtual console port
| Bits | Name | Mode | Reset | Description |
|------+-------+------+-------+---------------------------------|
| 9 | VALID | RO | 0 | Symbol in DATA is valid |
| 8 | SEND | HS | 0 | Write 1 to send symbol |
| 7:0 | DATA | RW | 0 | Read/write char from/to console |

98
source/io_reg.v Normal file
View File

@ -0,0 +1,98 @@
// This file is auto-generated. Do not edit
module io_reg
(input wire clock,
input wire reset,
/* ---- Access bus ---- */
/* verilator lint_off UNUSED */
input wire [31:0] i_addr,
input wire [31:0] i_data,
output wire [31:0] o_data,
input wire [3:0] i_ben,
input wire i_write,
input wire i_read,
/* verilator lint_on UNUSED */
/* ---- 'ctrl' ---- */
output wire o_ctrl_stop,
/* ---- 'console' ---- */
output wire o_console__rnotify,
input wire [7:0] i_console_data,
output wire [7:0] o_console_data,
output wire o_console_send_hsreq,
input wire i_console_send_hsack,
input wire i_console_send,
input wire i_console_valid);
/* ---- Address decoder ---- */
wire ctrl_select;
wire console_select;
assign ctrl_select =
i_addr[2] == 1'b0;
assign console_select =
i_addr[2] == 1'b1;
/* ---- 'ctrl' ---- */
reg ctrl_stop;
assign o_ctrl_stop = ctrl_stop;
always @(posedge clock)
if (reset)
ctrl_stop <= 1'b0;
else
if (ctrl_select && i_write) begin
if (i_ben[0]) ctrl_stop <= i_data[0];
end
/* ---- 'console' ---- */
reg [7:0] console_data;
assign o_console_data = console_data;
always @(posedge clock)
if (reset)
console_data <= 8'b0;
else
if (console_select && i_write) begin
if (i_ben[0]) console_data[7:0] <= i_data[7:0];
end
reg console_send_hsreq;
assign o_console_send_hsreq = console_send_hsreq;
always @(posedge clock)
if (reset)
console_send_hsreq <= 1'b0;
else begin
if (console_select && i_write && i_ben[1]) console_send_hsreq <= i_data[8];
else console_send_hsreq <= console_send_hsreq & (~i_console_send_hsack);
end
assign o_console__rnotify = console_select & i_read;
/* ---- Read multiplexer ---- */
reg [31:0] data_ctrl;
reg [31:0] data_console;
assign o_data =
data_ctrl |
data_console;
always @(*) begin
data_ctrl = 32'd0;
data_console = 32'd0;
if (console_select) begin
data_console[7:0] = i_console_data;
data_console[8] = i_console_send;
data_console[9] = i_console_valid;
end
end
endmodule // io_reg

3044
source/picorv32.v Normal file

File diff suppressed because it is too large Load Diff

179
source/picorv32_tcm.sv Normal file
View File

@ -0,0 +1,179 @@
`timescale 1ps/1ps
module picorv32_tcm #(parameter ADDR_WIDTH = 8,
parameter USE_LOOK_AHEAD = 0,
parameter USE_ADDR_MUX = 1,
parameter MEM_INIT_FILE = "")
(input wire clock,
/* verilator lint_off UNUSED */
// Not used in look-ahead mode
input wire reset,
/* verilator lint_on UNUSED */
/* PicoRV32 bus interface */
input wire mem_valid,
output wire mem_ready,
input wire [ADDR_WIDTH-1:0] mem_addr,
input wire [31:0] mem_wdata,
input wire [3:0] mem_wstrb,
output reg [31:0] mem_rdata,
// PicoRV32 look-ahead address.
// Not used in non-look-ahead mode.
/* verilator lint_off UNUSED */
input wire [ADDR_WIDTH-1:0] mem_la_addr
/* verilator lint_off UNUSED */
);
logic [31:0] ram[0:(2 ** (ADDR_WIDTH-2))-1];
if (MEM_INIT_FILE != "")
initial $readmemh(MEM_INIT_FILE, ram);
/* verilator lint_off UNUSED */
// Bits [1:0] are not used
logic [ADDR_WIDTH-1:0] byte_addr;
/* verilator lint_on UNUSED */
logic [ADDR_WIDTH-3:0] word_addr;
logic write;
assign word_addr = byte_addr[ADDR_WIDTH-1:2];
always_ff @(posedge clock) begin
for (int n = 0; n < 4; n += 1)
if (write && mem_wstrb[n])
ram[word_addr][n*8 +: 8] <= mem_wdata[n*8 +: 8];
mem_rdata <= ram[word_addr];
end
if (USE_LOOK_AHEAD == 0) begin : no_look_ahead
logic data_ready;
always_ff @(posedge clock)
if (reset) data_ready <= 1'b0;
else
// Don't use ternary operator to prevent
// X-propagation from PicoRV32 core
// data_ready <= mem_valid & ~(|mem_wstrb);
if (mem_valid && mem_wstrb == '0)
data_ready <= 1'b1;
else
data_ready <= 1'b0;
assign byte_addr = mem_addr;
assign write = mem_valid & (|mem_wstrb);
assign mem_ready = data_ready | write;
end
else begin : look_ahead
logic data_ready;
always_ff @(posedge clock)
if (reset) data_ready <= 1'b0;
else
// Don't use ternary operator to prevent
// X-propagation from PicoRV32 core
// data_ready <= ~(mem_valid && (|mem_wstrb));
if (mem_valid && mem_wstrb != '0)
data_ready <= 1'b0;
else
data_ready <= 1'b1;
/* mem_la_addr валиден как минимум один такт после поднятия mem_valid.
* Т.е. в принципе можно обойтись без мультиплескора. В формальной части
* добавлено соответствуюшее утверждение. */
if (USE_ADDR_MUX == 0)
assign byte_addr = mem_la_addr[ADDR_WIDTH-1:0];
else
assign byte_addr = mem_valid ?
mem_addr[ADDR_WIDTH-1:0] :
mem_la_addr[ADDR_WIDTH-1:0];
assign write = mem_valid & (|mem_wstrb);
assign mem_ready = data_ready;
end
`ifdef FORMAL
// Past valid flag
logic have_past = 1'b0;
always_ff @(posedge clock) have_past <= 1'b1;
// Assumptions
always @(*) assume(reset == 1'b0);
always @(posedge clock)
if (have_past) begin
// mem_addr <= mem_la_addr
if ($rose(mem_valid))
assume(mem_addr == $past(mem_la_addr));
// Stable mem_addr and mem_data when mem_valid is active
if (mem_valid) begin
assume($stable(mem_addr));
assume($stable(mem_wdata));
assume($stable(mem_wstrb));
end
// Assume mem_valid will not cleared while mem_ready is not active
if ($past(mem_valid) && !$past(mem_ready))
assume(mem_valid == 1'b1);
// Assume mem_valid will cleared after memory transaction complete
if ($past(mem_valid) && $past(mem_ready))
assume(mem_valid == 1'b0);
// WARN: May be wrong assumption
// Assume mem_add == mem_la_addr on first clock cycle of mem_valid activity
if ($rose(mem_valid))
assume(mem_addr == mem_la_addr);
end
else begin
// Initial mem_valid = 1'b0
assume(mem_valid == 1'b0);
end
// Data read
always_ff @(posedge clock)
if (have_past)
if (mem_valid && mem_ready && mem_wstrb == '0)
assert(mem_rdata == ram[mem_addr[ADDR_WIDTH-1:2]]);
// Data write
always_ff @(posedge clock) begin
logic [3:0] mem_wstrb_past;
if (have_past) begin
mem_wstrb_past = $past(mem_wstrb);
if ($past(mem_valid) && $past(mem_ready) && mem_wstrb_past != '0)
for (int n = 0; n < 4; n += 1)
if (mem_wstrb_past[n])
assert(ram[$past(mem_addr[ADDR_WIDTH-1:2])][n*8 +: 8] == $past(mem_wdata[n*8 +: 8]));
end
end
// Mem ready
always_ff @(posedge clock)
if (have_past)
if (USE_LOOK_AHEAD == 0) begin
// Write transaction
if (mem_wstrb != '0 && mem_valid)
assert(mem_ready);
// First cycle of read transaction
if (mem_wstrb == '0 && !$past(mem_valid)&& mem_valid)
assert(!mem_ready);
// Second cycle of read transaction
if ($past(mem_wstrb) == '0 && $past(mem_valid) && mem_valid)
assert(mem_ready);
end
else begin
// In look-ahead mode mem_ready always active
if (mem_valid)
assert(mem_ready);
end
`endif
endmodule // picorv32_tcm

5
source/sources.f Normal file
View File

@ -0,0 +1,5 @@
../source/bus_mux.v
../source/io_reg.v
../source/picorv32_tcm.sv
../source/picorv32.v
../source/testbench.sv

211
source/testbench.sv Normal file
View File

@ -0,0 +1,211 @@
`timescale 1ps/1ps
module testbench (input clock);
parameter MEM_ADDR_WIDTH = 16;
logic reset = 1'b1;
/* verilator lint_off UNUSED */
logic cpu_mem_valid;
logic cpu_mem_instr;
logic cpu_mem_ready;
logic [31:0] cpu_mem_addr;
logic [31:0] cpu_mem_wdata;
logic [ 3:0] cpu_mem_wstrb;
logic [31:0] cpu_mem_rdata;
// Look-Ahead Interface
logic cpu_mem_la_read;
logic cpu_mem_la_write;
logic [31:0] cpu_mem_la_addr;
logic [31:0] cpu_mem_la_wdata;
logic [ 3:0] cpu_mem_la_wstrb;
/* verilator lint_on UNUSED */
// PicoRV32 // Defaults
picorv32 #(.ENABLE_COUNTERS(0), // = 1,
.ENABLE_COUNTERS64(0), // = 1,
.ENABLE_REGS_16_31(1), // = 1,
.ENABLE_REGS_DUALPORT(1), // = 1,
.LATCHED_MEM_RDATA(0), // = 0,
.TWO_STAGE_SHIFT(1), // = 1,
.BARREL_SHIFTER(0), // = 0,
.TWO_CYCLE_COMPARE(0), // = 0,
.TWO_CYCLE_ALU(0), // = 0,
.COMPRESSED_ISA(0), // = 0,
.CATCH_MISALIGN(1), // = 1,
.CATCH_ILLINSN(1), // = 1,
.ENABLE_PCPI(0), // = 0,
.ENABLE_MUL(0), // = 0,
.ENABLE_FAST_MUL(0), // = 0,
.ENABLE_DIV(0), // = 0,
.ENABLE_IRQ(0), // = 0,
.ENABLE_IRQ_QREGS(0), // = 1,
.ENABLE_IRQ_TIMER(0), // = 1,
.ENABLE_TRACE(0), // = 0,
.REGS_INIT_ZERO(0), // = 0,
.MASKED_IRQ(32'h 0000_0000), // = 32'h 0000_0000,
.LATCHED_IRQ(32'h ffff_ffff), // = 32'h ffff_ffff,
.PROGADDR_RESET(32'h 0000_0000), // = 32'h 0000_0000,
.PROGADDR_IRQ(32'h 0000_0010), // = 32'h 0000_0010,
.STACKADDR(32'h ffff_ffff)) // = 32'h ffff_ffff
picorv32
(.clk(clock),
.resetn(~reset),
.mem_valid(cpu_mem_valid), // output reg
.mem_instr(cpu_mem_instr), // output reg
.mem_ready(cpu_mem_ready), // input
.mem_addr(cpu_mem_addr), // output reg [31:0]
.mem_wdata(cpu_mem_wdata), // output reg [31:0]
.mem_wstrb(cpu_mem_wstrb), // output reg [ 3:0]
.mem_rdata(cpu_mem_rdata), // input [31:0]
// Look-Ahead Interface
.mem_la_read(cpu_mem_la_read), // output
.mem_la_write(cpu_mem_la_write), // output
.mem_la_addr(cpu_mem_la_addr), // output [31:0]
.mem_la_wdata(cpu_mem_la_wdata), // output reg [31:0]
.mem_la_wstrb(cpu_mem_la_wstrb), // output reg [ 3:0]
// Unused
/* verilator lint_off PINCONNECTEMPTY */
.pcpi_valid(), // output reg
.pcpi_insn(), // output reg [31:0]
.pcpi_rs1(), // output [31:0]
.pcpi_rs2(), // output [31:0]
.pcpi_wr(1'b0), // input
.pcpi_rd(32'd0), // input [31:0]
.pcpi_wait(1'b0), // input
.pcpi_ready(1'b0), // input
.irq(32'd0), // input [31:0]
.eoi(), // output reg [31:0]
.trap(), // output reg
.trace_valid(), // output reg
.trace_data() // output reg [35:0]
/* verilator lint_on PINCONNECTEMPTY */
);
// -- Bus multiplexer
// Slaves address ranges:
// 0 - 0x00000000-0x0000ffff
// 1 - 0x01000000-0x01000fff
// i_slave_rdata bits:
// 0: i_slave_rdata[31:0]
// 1: i_slave_rdata[63:32]
logic [31:0] rdata_ram;
logic [31:0] rdata_reg;
logic valid_ram;
logic ready_ram;
logic valid_reg;
logic ready_reg;
bus_mux bus_mux
(.clock, .reset,
// CPU
.i_la_addr(cpu_mem_la_addr),
.o_rdata(cpu_mem_rdata),
.i_valid(cpu_mem_valid),
.o_ready(cpu_mem_ready),
// Slaves
.i_slave_rdata({rdata_reg, rdata_ram}),
.o_slave_valid({valid_reg, valid_ram}),
.i_slave_ready({ready_reg, ready_ram}));
// -- CPU memory
picorv32_tcm #(.ADDR_WIDTH(MEM_ADDR_WIDTH),
.USE_LOOK_AHEAD(1),
.USE_ADDR_MUX(0),
.MEM_INIT_FILE("../source/firmware/fw.mem"))
picorv32_tcm
(.clock, .reset,
/* PicoRV32 bus interface */
.mem_valid(valid_ram),
.mem_ready(ready_ram),
.mem_addr(cpu_mem_addr[MEM_ADDR_WIDTH-1:0]),
.mem_wdata(cpu_mem_wdata),
.mem_wstrb(cpu_mem_wstrb),
.mem_rdata(rdata_ram),
.mem_la_addr(cpu_mem_la_addr[MEM_ADDR_WIDTH-1:0]));
// -- Registers
// Reg 'ctrl'
logic ctrl_stop;
// Reg 'console'
logic [7:0] i_console_data;
logic [7:0] o_console_data;
logic console_send;
logic reg_write;
logic reg_read;
assign ready_reg = 1'b1;
assign reg_write = valid_reg & |(cpu_mem_wdata);
assign reg_read = valid_reg & &(~cpu_mem_wdata);
assign i_console_data = 8'ha5;
io_reg io_reg
(.clock, .reset,
// CPU
.i_addr({16'd0, cpu_mem_addr[15:0]}),
.i_data(cpu_mem_wdata),
.o_data(rdata_reg),
.i_ben(cpu_mem_wstrb),
.i_write(reg_write),
.i_read(reg_read),
// Reg 'ctrl'
.o_ctrl_stop(ctrl_stop),
// Reg 'console'
.i_console_data(i_console_data),
.o_console_data(o_console_data),
.o_console_send_hsreq(console_send),
// Unused
/* verilator lint_off PINCONNECTEMPTY */
.o_console__rnotify(),
.i_console_send_hsack(1'b1),
.i_console_send(1'b0),
.i_console_valid(1'b1)
/* verilator lint_on PINCONNECTEMPTY */
);
// Reset
localparam RESET_DURATION = 5;
int reset_counter = RESET_DURATION;
always_ff @(posedge clock)
if (reset_counter == 0)
reset <= 1'b0;
else
reset_counter <= reset_counter - 1;
// Print console output
// always_ff @(posedge clock)
// if (!reset && console_send)
// $write("%c", o_console_data);
initial
forever begin
@(posedge clock);
if (!reset && console_send) begin
$write("%c", o_console_data);
$fflush;
end
end
// Wait for complete
initial begin
while (reset || ctrl_stop == 1'b0) @(posedge clock);
@(posedge clock);
$finish;
end
endmodule // testbench

View File

@ -0,0 +1 @@
((verilog-mode . ((flycheck-verilator-include-path . ("../source")))))

1
test-iverilog/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
top

3
test-iverilog/__build.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
iverilog -g2012 -o top -f ../source/sources.f top.sv

3
test-iverilog/__run.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
vvp -n ./top

7
test-iverilog/top.sv Normal file
View File

@ -0,0 +1,7 @@
`timescale 1ps/1ps
module top;
logic clock = 1'b0;
initial forever #(10ns/2) clock = ~clock;
testbench testbench (clock);
endmodule

View File

@ -0,0 +1 @@
((verilog-mode . ((flycheck-verilator-include-path . ("../source")))))

2
test-modelsim/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
testbench
transcript

5
test-modelsim/__build.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e
rm -rf testbench
vlog -sv -work testbench -vopt -f ../source/sources.f top.sv

3
test-modelsim/__run.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
vsim -c -batch -voptargs=+acc=npr -do "run -all" -quiet -lib testbench top

7
test-modelsim/top.sv Normal file
View File

@ -0,0 +1,7 @@
`timescale 1ps/1ps
module top;
logic clock = 1'b0;
initial forever #(10ns/2) clock = ~clock;
testbench testbench (clock);
endmodule

View File

@ -0,0 +1,38 @@
BasedOnStyle: LLVM
IndentWidth: 4
UseTab: false
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AlignConsecutiveMacros: true
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments: true
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: WithoutElse
BinPackArguments: true
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterControlStatement: MultiLine
AfterFunction: true
AfterStruct: true
BeforeElse: true
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: false
IncludeBlocks: Regroup
SpaceBeforeParens: ControlStatements
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 4
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false

2
test-verilator/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
testbench
compile_flags.txt

17
test-verilator/Makefile Normal file
View File

@ -0,0 +1,17 @@
TOP_MODULE = testbench
SOURCES = top.cpp clock_generator.cpp
FLAGS_FILE = ../source/sources.f
INCLUDES =
FLAGS = -Wno-WIDTH -cc --top-module $(TOP_MODULE) +1800-2017ext+sv \
--timing --Mdir $(TOP_MODULE) -o $(TOP_MODULE) -f $(FLAGS_FILE) \
--timescale "1ps/1ps" --threads 1
# FLAGS += --trace
all: $(SOURCES)
verilator $(FLAGS) --exe --build $(INCLUDES) $(SOURCES)
clean:
rm -rf $(TOP_MODULE)

5
test-verilator/__build.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e
make clean
make

3
test-verilator/__run.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
./testbench/testbench

View File

@ -0,0 +1,95 @@
#include "clock_generator.hpp"
ClockGenerator::~ClockGenerator()
{
while (clocks) {
clock *next = clocks->next;
delete clocks;
clocks = next;
}
};
void ClockGenerator::add_clock(uint8_t &net, uint64_t period, uint64_t skew)
{
if (skew >= period) throw "The skew value cannot exceed the period";
clock *clk = new clock(net, period, skew);
net = (clk->position < clk->period / 2) ? 0 : 1;
if (clocks == NULL)
clocks = clk;
else {
clock *last = clocks;
while (last->next)
last = last->next;
last->next = clk;
}
};
uint64_t ClockGenerator::next_event(void)
{
uint64_t time_to_next = UINT64_MAX;
clock *clk = clocks;
while (clk) {
uint64_t ttn;
if (clk->position < clk->period / 2)
ttn = clk->period / 2 - clk->position;
else
ttn = clk->period - clk->position;
if (time_to_next > ttn) time_to_next = ttn;
clk = clk->next;
}
clk = clocks;
while (clk) {
uint8_t next_val;
clk->position += time_to_next;
if (clk->position >= clk->period) clk->position -= clk->period;
next_val = (clk->position < clk->period / 2) ? 0 : 1;
clk->posedge = (next_val == 1 && clk->net == 0) ? true : false;
clk->negedge = (next_val == 0 && clk->net == 1) ? true : false;
clk->net = next_val;
clk = clk->next;
}
return time_to_next;
};
bool ClockGenerator::is_posegde(uint8_t &net)
{
clock *clk = clocks;
bool posedge = false;
while (clk) {
if (std::addressof(net) == std::addressof(clk->net)) {
posedge = clk->posedge;
break;
}
clk = clk->next;
}
return posedge;
};
bool ClockGenerator::is_negedge(uint8_t &net)
{
clock *clk = clocks;
bool negedge = false;
while (clk) {
if (std::addressof(net) == std::addressof(clk->net)) {
negedge = clk->negedge;
break;
}
clk = clk->next;
}
return negedge;
};

View File

@ -0,0 +1,31 @@
#ifndef _CLOCK_GENERATOR_HPP
#define _CLOCK_GENERATOR_HPP
#include <cstdint>
#include <memory>
class ClockGenerator
{
protected:
struct clock {
clock(uint8_t &net, uint64_t period, uint64_t position) :
net(net), period(period), position(position), next(NULL),
posedge(false), negedge(false) {};
uint8_t &net;
uint64_t period;
uint64_t position;
bool posedge;
bool negedge;
clock *next;
} *clocks = NULL;
public:
~ClockGenerator();
void add_clock(uint8_t &net, uint64_t period, uint64_t skew);
uint64_t next_event(void);
bool is_posegde(uint8_t &net);
bool is_negedge(uint8_t &net);
};
#endif // _CLOCK_GENERATOR_HPP

19
test-verilator/shell.nix Normal file
View File

@ -0,0 +1,19 @@
{ pkgs ? import <nixpkgs> {} }:
with pkgs;
let
flags-file = "compile_flags.txt";
in
mkShell {
packages = [ gnumake verilator ];
shellHook = ''
echo -n > ${flags-file}
echo -DVM_TRACE=1 >> ${flags-file}
echo -xc++ >> ${flags-file}
echo -I./testbench >> ${flags-file}
echo -I${verilator}/share/verilator/include >> ${flags-file}
echo -I${clang}/resource-root/include >> ${flags-file}
echo -I${glibc.dev}/include >> ${flags-file}
'';
}

74
test-verilator/top.cpp Normal file
View File

@ -0,0 +1,74 @@
#include "Vtestbench.h"
#include "clock_generator.hpp"
#include <cstdint>
#include <verilated.h>
#include <verilated_vcd_c.h>
#define DUMPFILE "testbench.vcd"
int main(int argc, char **argv)
{
VerilatedContext *ctx = new VerilatedContext;
ctx->commandArgs(argc, argv);
/* Create model instance */
Vtestbench *top = new Vtestbench(ctx);
#if (VM_TRACE == 1)
VerilatedVcdC *vcd = new VerilatedVcdC;
ctx->traceEverOn(true);
top->trace(vcd, 99);
vcd->open(DUMPFILE);
#endif
/* Create clock source */
ClockGenerator *clk = new ClockGenerator;
/* Add clocks and go to first event */
clk->add_clock(top->clock, 10000, 0);
clk->next_event();
/* Cycle counter */
uint64_t cycle = 0;
/* ---- Evaluation loop ---- */
while (!ctx->gotFinish()) {
/* Clock event */
ctx->timeInc(clk->next_event());
/* Get output values (before clock edge) */
if (clk->is_posegde(top->clock)) {
// NOP
}
/* Eval */
top->eval();
/* Put input values (after clock edge)*/
if (clk->is_posegde(top->clock)) {
// NOP
}
/* Trace steady-state values */
#if (VM_TRACE == 1)
if (vcd) vcd->dump(ctx->time());
#endif
cycle ++;
}
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;
}