diff --git a/tests/functional/simulate_rosette.rkt b/tests/functional/simulate_rosette.rkt index 39038d0cd..aa838a5a0 100644 --- a/tests/functional/simulate_rosette.rkt +++ b/tests/functional/simulate_rosette.rkt @@ -1,104 +1,157 @@ ; Utilities for simulating Rosette programs. +; +; Tests can be run with `raco test `. #lang racket/base (provide simulate-rosette) (require (only-in rosette bv) - racket/match racket/list) ; Inputs: ; - function: The function for the module to simulate. This should be a Rosette function generated by ; Yosys's `write_fuctional_rosette` backend. -; - input-helper, output-helper: association-list-based helpers for input and output struct, generated -; by Yosys's `write_fuctional_rosette` backend with `-assoc-list-helpers` enabled. ; - initial-state: The initial state of the module, as generated by Yosys's `write_fuctional_rosette` ; backend. -(define (simulate-rosette #:function function - #:input-helper input-helper - #:output-helper output-helper - #:initial-state initial-state - #:inputs inputs - #:outputs outputs) - (error "TODO: Implement simulate-rosette function")) +; - inputs: A list of association lists. The function will be called with each association list as +; inputs, and the state will be threaded through each call. +; +; Outputs: +; - A list of outputs, one for each cycle. The outputs are a list of the output objects generated by +; `function`. +(define (simulate-rosette #:function function #:initial-state initial-state #:inputs inputs) + + (define outputs-and-states + (drop (reverse (foldl (lambda (input acc) + (let* ([outputs (function input (cdr (car acc)))]) (cons outputs acc))) + (list (cons 'unused initial-state)) + inputs)) + 1)) + + (define outputs (map car outputs-and-states)) + + outputs) ; Inputs: -; - config: Association list mapping input name (string) to a configuration value, which is one of the -; following: -; - 'exhaustive: The input should be exhaustively tested. -; - : The input should be tested with this many random inputs. When the input is not -; present in the config, it defaults to 'exhaustive. -; - -(define (generate-inputs #:input-helper input-helper - #:num-inputs num-inputs - #:config config - #:inputs inputs) - ; Fill out missing vallues in the config with 'exhaustive. - (define config - (map (λ (input) - (let ([found (assoc (car input) config)]) (or found (cons (car input) 'exhaustive)))) - inputs)) +; - inputs: association list mapping string name to bitwidth. +; - num-inputs: number of inputs to generate. +; TODO(@gussmith23): If `num-inputs` is more than the number of possible values, just enumerate. +(define (generate-inputs #:inputs inputs #:num-inputs num-inputs) + (define (generate-random-input inputs) + (map (lambda (pair) (cons (car pair) (bv (random (expt 2 (cdr pair))) (cdr pair)))) inputs)) + (for/list ([_ (range num-inputs)]) + (generate-random-input inputs))) - ; ; Generate the inputs. - ; (define generated-inputs - ; (map (λ (input) - ; (let ([input-name (car input)] [input-bitwidth (cdr input)]) - ; (cond - ; [(equal? 'exhaustive (cdr (assoc input-name config))) (list input-name 'exhaustive)] - ; [(number? (cdr (assoc input-name config))) - ; (list input-name (make-random-input input-type (cdr (assoc input-name config))))] - ; [else (error "Invalid configuration for input" input-name)]))) - ; inputs)) - - (error "TODO")) - -; Helper function: for a given input name, bitwidth, and configuration, generate a list of inputs. -; Output: List of Rosette bitvector values for the input. -(define (generate-inputs-for-one input-name bitwidth config) - (cond - ; If the configuration is 'exhaustive, or if they request a number of inputs that is greater than - ; or equal to the number of possible values for the bitwidth, generate all possible inputs. - [(or (equal? config 'exhaustive) (and (number? config) (>= config (expt 2 bitwidth)))) - (for/list ([n (range (expt 2 bitwidth))]) - (bv n bitwidth))] - [(and (number? config) (positive? config)) - (map (λ (_) (bv (random (expt 2 bitwidth)) bitwidth)) (range config))] - [else (error (format "Invalid configuration ~a for input ~a" config input-name))])) +; Generates a clock signal for the given inputs. +; +; Given a string of inputs, one per clock cycle, this function generates a clock signal alongside the +; inputs. It does so by alternating the clock signal between 0 and 1 for each cycle, starting with 0. +; For example, if the inputs are (list inputs1 inputs2 inputs3), the output will be (list (cons (cons +; "clk" (bv 0 1)) inputs1) (cons (cons "clk" (bv 1 1)) inputs1) (cons (cons "clk" (bv 0 1)) inputs2) +; (cons (cons "clk" (bv 1 1)) inputs2) ... ). +; +; Inputs: +; - clock-name: The name of the clock signal. +; - inputs: A list of inputs in association list form, as output by `generate-inputs`. +; +; Outputs: +; - A list of association lists, each containing a new clock signal. Will be twice the length of the +; inputs list. +(define (generate-clock #:clock-name clock-name #:inputs inputs) + (apply append + (for/list ([this-cycle-inputs inputs]) + (list (cons (cons clock-name (bv 0 1)) this-cycle-inputs) + (cons (cons clock-name (bv 1 1)) this-cycle-inputs))))) ; This is what gets executed when the script is run. (module main racket/base - (require racket/cmdline)) + (require racket/cmdline) + + ; - input-helper, output-helper: association-list-based helpers for input and output struct, generated + ; by Yosys's `write_fuctional_rosette` backend with `-assoc-list-helpers` enabled. + ) (module+ test - (require rackunit) - (test-case "generate-inputs-for-one" - (check-equal? (generate-inputs-for-one "input1" 4 'exhaustive) - (list (bv 0 4) - (bv 1 4) - (bv 2 4) - (bv 3 4) - (bv 4 4) - (bv 5 4) - (bv 6 4) - (bv 7 4) - (bv 8 4) - (bv 9 4) - (bv 10 4) - (bv 11 4) - (bv 12 4) - (bv 13 4) - (bv 14 4) - (bv 15 4))) + (require rackunit + (only-in rosette bv bvadd)) + (test-case "generate-inputs" + (check-equal? (length (generate-inputs #:inputs (list (cons "input1" 4)) #:num-inputs 10)) 10) + ; Check that this call generates a list of one-length lists, each containing a single association + ; list with the key "input1" and a random value. + (check-true (foldl (lambda (input acc) + (and acc (equal? (length input) 1) (equal? (car (first input)) "input1"))) + #t + (generate-inputs #:inputs (list (cons "input1" 4)) #:num-inputs 10)))) - ; Requesting fewer inputs than the number of possible values for the bitwidth. - (check-equal? (length (generate-inputs-for-one "input2" 3 5)) 5) + (test-case "generate-clock" + (define inputs + (list (list (cons "input1" (bv 4 4)) (cons "input2" (bv 3 3)) (cons "input3" (bv 2 2))) + (list (cons "input1" (bv 3 4)) (cons "input2" (bv 4 3)) (cons "input3" (bv 1 2))) + (list (cons "input1" (bv 2 4)) (cons "input2" (bv 5 3)) (cons "input3" (bv 0 2))))) - ; Requesting more inputs than the number of possible values for the bitwidth. - (check-equal? (generate-inputs-for-one "input3" 2 5) (list (bv 0 2) (bv 1 2) (bv 2 2) (bv 3 2))) + (check-equal? (length (generate-clock #:clock-name "clk" #:inputs inputs)) 6) - ; Requesting equal number of inputs as the number of possible values for the bitwidth. - (check-equal? (generate-inputs-for-one "input4" 2 4) (list (bv 0 2) (bv 1 2) (bv 2 2) (bv 3 2))) + (check-equal? + (generate-clock #:clock-name "clk" #:inputs inputs) + (list (cons (cons "clk" (bv 0 1)) + (list (cons "input1" (bv 4 4)) (cons "input2" (bv 3 3)) (cons "input3" (bv 2 2)))) + (cons (cons "clk" (bv 1 1)) + (list (cons "input1" (bv 4 4)) (cons "input2" (bv 3 3)) (cons "input3" (bv 2 2)))) + (cons (cons "clk" (bv 0 1)) + (list (cons "input1" (bv 3 4)) (cons "input2" (bv 4 3)) (cons "input3" (bv 1 2)))) + (cons (cons "clk" (bv 1 1)) + (list (cons "input1" (bv 3 4)) (cons "input2" (bv 4 3)) (cons "input3" (bv 1 2)))) + (cons (cons "clk" (bv 0 1)) + (list (cons "input1" (bv 2 4)) (cons "input2" (bv 5 3)) (cons "input3" (bv 0 2)))) + (cons (cons "clk" (bv 1 1)) + (list (cons "input1" (bv 2 4)) (cons "input2" (bv 5 3)) (cons "input3" (bv 0 2))))))) + (test-case "simulate-rosette" - ; Requesting invalid configuration should raise an error. - (check-exn exn:fail? (λ () (generate-inputs-for-one "input5" 2 'invalid-config))) - (check-exn exn:fail? (λ () (generate-inputs-for-one "input5" 2 bytes->string/latin-1))))) + ; This function will take association lists as inputs, so the helper function is simply identity. + ; This is not generally true of Yosys-generated code. Similarly, this function uses an association + ; list for state, which is not what Yosys generates, but it's easier for testing. + ; + ; A one-stage adder. Inputs are registered in one clock cycle, and the output is the sum of the + ; two registered inputs. + (define (module-function inputs state) + (let* ([a (cdr (assoc "a" inputs))] + [b (cdr (assoc "b" inputs))] + [clk (cdr (assoc "clk" inputs))] + [old-clk (cdr (assoc "clk" state))] + [prev-a (cdr (assoc "prev-a" state))] + [prev-b (cdr (assoc "prev-b" state))] + [a-reg (cdr (assoc "a-reg" state))] + [b-reg (cdr (assoc "b-reg" state))] + [clk-ticked (and (equal? clk (bv 1 1)) (equal? old-clk (bv 0 1)))] + [new-a-reg (if clk-ticked prev-a a-reg)] + [new-b-reg (if clk-ticked prev-b b-reg)] + [out (list (cons "o" (bvadd new-a-reg new-b-reg)))] + [new-state (list (cons "prev-a" a) + (cons "a-reg" new-a-reg) + (cons "prev-b" b) + (cons "b-reg" new-b-reg) + (cons "clk" clk))]) + (cons out new-state))) + + (define outputs + (simulate-rosette #:function module-function + #:initial-state (list (cons "a-reg" (bv 0 4)) + (cons "b-reg" (bv 0 4)) + (cons "prev-a" (bv 0 4)) + (cons "prev-b" (bv 0 4)) + (cons "clk" (bv 0 1))) + #:inputs + (list (list (cons "clk" (bv 0 1)) (cons "a" (bv 4 4)) (cons "b" (bv 4 4))) + (list (cons "clk" (bv 1 1)) (cons "a" (bv 3 4)) (cons "b" (bv 0 4))) + (list (cons "clk" (bv 0 1)) (cons "a" (bv 10 4)) (cons "b" (bv 9 4))) + (list (cons "clk" (bv 1 1)) (cons "a" (bv 2 4)) (cons "b" (bv -1 4))) + (list (cons "clk" (bv 0 1)) (cons "a" (bv 4 4)) (cons "b" (bv -15 4))) + (list (cons "clk" (bv 1 1)) (cons "a" (bv 0 4)) (cons "b" (bv 0 4)))))) + + (check-equal? outputs + (list (list (cons "o" (bv 0 4))) + (list (cons "o" (bv 8 4))) + (list (cons "o" (bv 8 4))) + (list (cons "o" (bv 3 4))) + (list (cons "o" (bv 3 4))) + (list (cons "o" (bv -11 4)))))))