-
Notifications
You must be signed in to change notification settings - Fork 1
/
README
995 lines (744 loc) · 44.6 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
bream/README.txt -- The latest revision Dec 15 2012 Evening by karttu.
[Short note added May 2 2019: Why this project languished? For one thing,
coding all nights long for over a year started taking toll on my health
(I was diagnosed as having a border-line Diabetes-II later that year),
and moreover, I was a bit disappointed how little feedback I got for
the project that I thought would be nothing short of revolutionary.
But I soon realized that most of the software people didn't understand
at all what the FPGA's are (standard question was: "How many cores they have?")
while most of the hardware people (with experience from FPGA's also) didn't
want to do anything else with them than just the traditional hardware stuff,
signal processing and so on. Then, in autumn 2011 I got a teaching gig at
Metropolia, which shifted my attention to other things.]
This directory/zip-packaged contains a pre-release version of Bream,
( Copyright (C) 2010-2012 by Antti Karttunen )
a simple compiler compiling nonstandard Scheme subset to Verilog.
The compiler itself is written in Scheme and its source code,
unless otherwise noted, is provided either under GPL v2
(the GNU General Public License version 2)
or LGPL v2 (the GNU Library General Public License, version 2)
(See file COPYING in appropriate directories).
So far, the system works to a degree that it can output Verilog code
that actually compiles allright with Xilinx ISE (WebPack),
both with version 11.1 and version 13.2, and for the many cases,
produces configurations that work as expected in FPGA.
(These have been tested with both Digilent's Spartan 3 XC3S200 Starter Board
and Digilent's new ATLYS-board featuring Xilinx Spartan-6 XC6SLX45 FPGA.)
However, you shouldn't expect anything to work (yet!)
Also, expect anything (syntax, semantics, function names) still
to change without warning.
Note: in this prerelease, the compiling of pseudocombinational
functions (i.e. the ones that need a clock but do not involve an explicit
tail-recursive loop as specified by the programmer) doesn't work in
many cases. As a work-around, you can define them with define-wirm instead.
For example,
(define (lcm a b) (*c a (quotient b (gcd a b))))
doesn't yet produce working state machine. But writing
the definition as:
(define-wirm (lcm a b) (*c a (quotient b (gcd a b))))
the code that calls gcd and quotient is directly instantiated
at the level of module that invokes lcm, and hopefully contains
itself a loop.
Please contact me at <my-firstname>.<my-surname>(AT)gmail.com
for the current developments, suggestions, any constructive criticism
or just plain bug reports.
-- Antti "karttu" Karttunen, Helsinki, August 28 2011.
I submitted the subdirectory Compiled_for_ATLYS into each
test-project under bream/testprojects with a various assortment
of compiled Verilog-files (*.v), from which you can get
some hunch of how the compiler works.
(Note: t_inttests currently computes A000010 incorrectly.
To be corrected.)
Some modifications to src/toplevel.scm concerning the naming
of I/O variables, and copying of UCF-files.
-- karttu, August 29 2011.
Changed the incorrectly named topmodule.scm in this document
to toplevel.scm as it should be. Now restarting the development
after a hiatus of almost 16 months. Committing this as a test
that my systems work.
-- karttu, December 15 2012.
------------------------------------------------------------------------
INTRODUCTION
"Bream" is loosely used here as the name of
(a) a new programming language intended for finely-grained parallel
programming, and
(b) the compiler that compiles a said programming language to
a hardware description language ("HDL", currently only Verilog)
that can be in turn compiled to FPGA-executable binary images
with tools provided by third-party vendors.
The current implementation of the compiler has been coded in programming
language Scheme, specifically, with variant that runs under the
releases 7.7.90.+ and 9.0.1 MIT/GNU Scheme. Or at least, those
are only Scheme implementations on which I have myself run the compiler.
The bream-language itself is based on Scheme, with some essentially
restrictions and additions that make it suitable for compiling
to FPGAs or other Hardware-platforms.
The fundamental execution model of Bream ("Bream FEM") is:
(a) PARALLEL BY DEFAULT,
(b) EAGER
and
(c) SPECULATIVE.
This means that, unless the programmer explicitly specifies otherwise,
(a & b) all sub-expressions of any expression (apart from the
"seq" forms listed below) are started at the same time,
and as early as possible (i.e. as soon as _their_ own
sub-expressions are ready).
and
(c) this simultaneous starting applies also to all branches
of conds and ifs, although only the return value of the
chosen branch is eventually returned to a calling form.
That is, for ordinary ifs, all three branches, the test-,
then- and else-branch are started simultaneously, and
only after the test-branch's result is ready, will the
choice be made between the then- and else-branch, which one's
result to return to a surrounding expression.
(Possibly after waiting the selected branch
to come ready, if it takes longer to compute than the
test-branch. See section Bream Function Interface Model below
for details).
There are also other important differences, as compared to standard Scheme.
We list the restrictions first:
RESTRICTIONS
The current intended basic functionality level of Bream is called
"Carpe Bream", for which pertain the following restrictions,
as compared to a standard Scheme:
- NO FULL-FLEDGED RECURSION. Only local tail-recursion
is supported, that is, any tail-recursive function which can
be rewritten as a named let.
For example, Euclid's famous algorithm for computing
the greatest common divisor of two integers:
(define (gcd a b)
(cond ((zero? b) a)
((>= a b) (gcd b (- a b)))
(else (gcd (- b a) a))
)
)
which can be rewritten to use the standard named let construct
of Scheme (and is actually rewritten in just such a way by
the syntax-expander before the compilation proper begins):
(define (gcd a b)
(let loop ((a a) (b b))
(cond ((zero? b) a)
((>= a b) (loop b (- a b)))
(else (loop (- b a) a))
)
)
)
(The full-fledged recursive calls MIGHT BE implemented in DISTANT future.
After all, most FPGA's contain Block-RAMs which can be used,
not just as FIFO's, but also as LIFO's, i.e. as stacks.)
- NO FIRST-CLASS FUNCTIONS. Currently one cannot pass a funarg
(i.e. a "function pointer" for C-programmers, assumed to refer
a set of functions with matching type signatures),
to another function. Instead, all functions invocations are "static"
in a sense, that the function invoked is always the same.
Some form of funargs SHOULD BE implemented in NEAR future.
Before that, their functionality can be mimicked (clumsily)
with hand-coded nested if/cond-constructs with explicit
"function selector codes".
- NO FLOATS, BIGNUMS, STRINGS, SYMBOLS, LISTS, VECTORS or any other fancy
data types apart from integers and booleans,
Yes, at Carpe Bream level, the only available types are INTEGERS
(but of any width, from one to thousands of bits!) and BOOLEANS.
Some forms of strings, symbols and lists MIGHT/SHOULD be implemented
in future levels, AFTER level Carpe. Also floating point numbers,
provided there is a popular demand for them.
Before that, at least vectors can be mimicked by packing smaller integers
to one large integer (bit string).
There is no much need for BIGNUMs (arbitrary size integers implemented
as lists of smaller chunks of binary vectors), as Bream's
integers can be specified with up to an arbitrary width,
limited only by available space in FPGA.
- INTEGERS CAN NOT BE MIXED WITH BOOLEANS.
Additionally, the type-resolver/checker of Bream requires that
any expression used as the test-expression of conditional forms
like "cond" and "if" HAS TO BE formally declared as of type boolean,
instead of being of any type of whatsoever, as allowed in Scheme and Lisp.
Similarly for arguments of logical connectives such as logand,
logor and lognot.
Note: internally boolean expressions are implemented just as
one-bit integers, and the requirement is just the current policy
in anticipation of future levels of Bream with a much larger type
repertoire. Then #f (boolean false) might eventually be implemented
differently from integer 1'0 (one-bit zero), also at the level of
produced, "physical" FPGA-code.
IF/WHEN we relax our current strict condition, we have to decide
whether integer 0 and boolean false act in test-expressions both
as false (the C-way) or not (the Lisp/Scheme way). By currently
requiring all test-expressions to be explicitly declared booleans,
we can defer that decision for a while being.
- NO RUN-TIME TYPE POLYMORPHISM.
At run-time the typing system of Carpe Bream is strictly static,
that is, in compiled FPGA code, any variable and expression can
be of only one type, that is currently, either a boolean or an integer
of some arbitrary but definite width.
However, although static, the typing system is not strict
in the sense that the programmer would need to explicitly declare
the type of each variable and expression before use.
Instead, the type-resolving module will try to automatically infere
the type of each expression and variable from programmer supplied
hints as well as it can.
This fact, together with wirm-macros, gives Bream a certain degree
of "compile-time type polymorphism", where for example, the gcd
function shown above can be compiled for any arbitrary width
arguments.
See the section TYPING below for more details about these latter
points.
Note that in future levels of Bream, we MIGHT implement a genuine
run-time dynamic typing ON THE TOP of existing static typing.
That is, the current static typing defines signals of certain widths,
but it is up to an application to assign meaning to these bit
vectors, thus, a subset of bits in each could stand for the
"run-time type" of the remaining bits.
- NO CONTINUATIONS. That is, there is no "call-with-current-continuation"
special form, nor any associated non-local ways to transfer control
or such non-deterministic forms as "amb" made possible by a clever
play with continuations. No plan to implement these.
- NO GLOBAL VARIABLES or DYNAMIC SCOPE of any kind. Everything
has a strictly lexical scope. No plans for it to be otherwise.
- NO SET! or similar special forms with SIDE-EFFECTS ON VARIABLES.
The only way programmer can assign new values to variables
(i.e. either the formal arguments of a function or lambda-form,
or the local variables specified in the beginning of let-forms)
is either
(a) by invoking a function or lambda-form with given call-args
or in various let-forms
(b) by initializing them to expressions given in the init-forms of let
and moreover, in the case of tail-recursive loops constructed with
named let form:
(c) by looping again,
with a set of expressions assigned to the variables, that will be valid
on the next iteration of the loop.
This is a DESIGN DECISION, which together with a strictly lexical scoping,
makes the semantics of bream-code transparent enough that it can be
simply and reliably compiled to a set of finite state machines that
can be naturally implemented on FPGA's.
- NO TRADITIONAL MACROS. Bream does not (currently) implement
macros, neither in old-fashioned "unhygienic" Common Lisp way
(defmacro, define-macro) or a new "hygienic" Scheme way (define-syntax).
Instead, it has recursive "wirm-macros", whose operation
can be controlled by the associated width of their arguments.
(See below, in section "additions".)
In future I MIGHT implement defmacro or define-syntax macros
provided they turn out to be essential or even useful.
- NO GARBAGE COLLECTION. As there is not yet any aggregate data
structures like lists supported, there is also no need for managing
their storage. Programmers are free to code (with bream, of course!)
their own memory management schemes, in case they want to employ
FPGA's internal Block-RAMs or FPGA-boards's external RAM in
non-trivial ways.
- SOME SYNTACTICAL RESTRICTIONS.
Bream DOES NOT ALLOW either an explicit or implicit "begin"
("progn" for Common Lispers) anywhere in the user code.
That is, any body of a function definition, a lambda-form,
a let-form or a then/else-branch of "cond" should contain
exactly ONE expression, no more.
In case the programmer wants to have multiple expressions
in such locations (presumably all except the last one intended
solely for their side-effects), he has to explicitly
contain them inside "par" or "seq" special form.
(See the next section for meaning of these forms).
The quote (') has wholly another significance in Bream than
in Lisp or Scheme. As (currently) there are no symbol or
list data types, it is not needed for specifying literal
("quoted") values of symbols or lists.
Instead, it is used for annotating the types of variables
and expressions. See the section TYPING below for more details.
Backquote (`) and comma (,) have (currently) no functionality and
can not be used in bream-code.
ADDITIONS
Bream has some essential additions and other fundamental differences to
standard Scheme.
- SEQUENTIAL FORMS
To give programmer a more explicit control over when to invoke functions
and other expressions, Bream offers forms like "seq" and "seq-if"
in addition to "par" and "if". They are especially useful
when invoking functions with side-effects (e.g. I/O-functions),
as to avoid any unnecessary extra calls.
The form (seq expr1 ... exprn) is otherwise like (par expr1 ... exprn),
in that it implements the functionality of begin (or "progn" in Lisp),
where each expression expr1, expr2, ... except the last one
is invoked only for its side-effects, and only the result of the last
one (exprn) is returned as a value to the surrounding form.
But while "par" starts all its expressions simultaneously,
the "seq" starts them in order, expr1, expr2, etc., not starting
the next one before the previous one has completed.
In this way, its functionality is identical to (begin expr1 ... exprn)
on standard sequential Von Neumann computing platforms.
(I.e. code compiled for ordinary (single-thread, single-core) CPU's).
Also, in contrast to speculative "if" of Bream, "seq-if"
resembles the standard "if-conditional" on sequential machines, in that
it will patiently wait for the test-expression to come ready
before it will start EITHER (but NOT both) of the test- or else-branches.
- TIME-DEPENDENT FORMS (to be implemented)
FASTEST, OR-F, AND-S, SLOWEST, etc. WAIT_UNTIL
- TYPING and TYPE-RESOLVING (INFERRING)
Ordinary Lisp and Scheme-systems employ dynamic (i.e. "run-time") typing,
where each data item is either associated with a few extra
tag bits, or some other trick (e.g. allocating different kind of objects
from separate areas of memory) is used to tell different types
from each other.
Also, integers come usually in discretely sized chunks, called
bytes (8 bits), words (usually either 16 or 32 bits), double words,
etc, as dictated by underlying computer architecture, and in Lisp/Scheme-
systems these sizes can be further decremented, because of those few
bits required for the run-time type tag.
In contrast to that, in Bream at run-time the type of each variable
and data signal is strictly determined. That is, all expressions
are currently interpreted either as integers of arbitrary but
definite width, or as booleans, that actually are just integers
of one bit width.
Furthermore, in our target language, Verilog (in future also VHDL),
the width of each signal has to be explicitly specified.
Thus, Bream-compiler has to determine in some fashion, what will
be the width of each variable and expression it encounters.
Assigning some standard fixed width (e.g. 32 bits, or even an arbitrary
value supplied e.g. as a command-line option to the compiler)
to all signals would lose much of the flexibility FPGAs offer.
Thus there has to be some way for a programmer to ANNOTATE any
variable or expression with its width. However, to avoid
the unsightly clutter and tedium of declaring every signal's
width (so typical for Verilog and VHDL code),
this annotation has to be OPTIONAL, with the type-resolving
subsystem of Bream-compiler capable of inferring the types
of the rest of any associated, but unannotated expressions
and variables.
If the programmer needs to explicitly annotate some part of the
code, the syntax for that is: type-specifier'expression.
For the current "Carpe" level of Bream-implementation,
the type-specifier is either
- a symbol "boolean"
- a symbol "output"
- or an integer from 1 onward.
Specifier output specifies that argument is assigned
the "output" specifier instead of the default "input"
in the produced Verilog-code. This is used just for
arguments to I/O-functions that need for example, a TXD-wire
of the UART which to send their data to.
One can also use a specifier like (output width)
(where width is an integer > 1) for output busses
wider than one bit.
Specifier boolean is used for expressions that result boolean
(true or false) value. In the produced Verilog code these are
just one-bit wires, but at the compile-time the distinction
from one-bit integers is enforced for the reasons mentioned
in RESTRICTIONS section above.
Otherwise, the type-specifier is really a specifier for
the width of bit vector (integer).
If the expression is a literal integer, the type-resolver
can infer that its width is AT LEAST the integer's size
when represented in binary system.
However, the programmer can also specify an explicit width
for literals by preceding the expression with an integer and
single quote.
E.g. 18'7 creates a binary string "000000000000000111",
where the three 1-bits of 7 are prefixed with fifteen 0-bits.
Specifying such widths explicitly also tells the type-resolver
that it should not try to change the width of that variable,
and also that any associated other expression should have
the same width.
The type-interference by association works as follows:
For any of the standard bitwise and arithmetic operations,
such as bitnot, bitand, bitor, bitxor, bitxnor, +, -, -1+, 1+ and *,
it is required that all the arguments as well as the result
have equal widths. Thus, if we have an expression like
(bitand a (bitxor b c))
and it is known that c's width is 6, then the type-resolver
can infer that also b's width is 6, and as well as that of
bitxor's result, thus also a and bitand's result are of six
bits both.
For conditional expressions like =, !=, <, <=, >, >=
it is known that the expression's result is of type boolean,
and the arguments are all integers of the same width.
For forms "if" and "seq-if" it is known (actually: required)
that their test-branch has (_should have_) an expression of
boolean return type, and that the the then- and else-branches
should have equal types (i.e. usually integers of the same width).
For bit-concatenating expression "conc", it is known
that the result's width is the sum of widths of arguments.
This fact can be used for inferring the width, not just of the result
from the arguments, but also the other way, e.g. if the result's
width is already known, but one of the arguments is unclear,
then its width can be computed as the difference of result's width and
the sum of other arguments (whose widths are known).
For the forms "bits" and "drop", which select a subset
of bits from their first argument, the result's width
can be computed from their other argument(s). Like with
"conc", the inferring process can be quite involved,
because of the complicated interplay of "definite" and "nondefinite"
widths.
For the special multiplication expression "*c" (multiply with combined
width), the type-resolving handler is same as for "conc",
i.e. the result's width is the sum of the widths of its arguments.
*c should be used when one needs to ensure that there
is space for the whole result of multiplication.
(E.g. multiplying an eight-bit integer 255 by another
eight-bit integer 253 results a sixteen bit integer 64515.)
Similarly, there is form +c2 which is a dyadic addition operation
that reserves an extra bit for possible carry out of the addition.
(XXX -- CHECK THIS, and also the functionality of +c folded to a
nested set of +c2's.)
See the source module typreslv.scm for more examples.
For cases where one needs to, say, add to variables
of non-equal widths, WITHOUT forcing the narrower one
to the width of the other one, one can use a special forms
"zxt" (zero extend) and "sxt" (sign extend).
Currently, only the first one is useful, as there is
hardly any real support for signed integers in Carpe-level
of Bream and in Verilog itself.
Also, there is form (zeqw var), which creates a zero of
the same width as what variable var has. This is often useful in
initializations of accumulators and other loop variables.
In contrast to many other typing regimes, the typing system
of Bream is not an additional bureaucratic chore leashed upon
the programmer, which offers him a little help except
saying "NO, you failed!", when he makes a mistake, but
actually HELPS the programmer to type LESS, and in conjunction
with wirm macros (see below), it is actually a component of
the system that essentially creates new code on the fly,
"generatively".
- WIRM MACROS
WIRMs (which are rumoured to stand for "Width-Induced Recursive Macros",
or maybe also for "Wonderfully Innovative Recursive Macros")
is another way to write Bream-code, apart from regular
Bream-functions.
Instead of
(define (foo arg1 arg2 ... argn) expr)
a wirm-macro called "foo" is written as
(define-wirm (foo arg1 arg2 ... argn) expr)
The topmost expression in wirm body is usually a "special if"
whose other branch can refer to the same wirm recursively.
In these cases there should be a clearly defined terminating
condition, the test-expression checking for the width of one
of the arguments (using a special form (w argx) that
returns the associated width of argx) to decide whether to
recurse any further, or otherwise, with the other branch
terminating the recursion.
(Note that there already IS a check for runaway recursion
in wirm-expander code.)
Wirm-macros are a handy way to utilize the BIT-LEVEL PARALLELISM
so lavishly offered by FPGAs, and to implement exotic operations
wholly combinationally in one clock cycle.
(Albeit it might need to be a _long_ (slow) cycle, if the expression
grows overtly complicated).
The body of wirm-macros is evaluated/expanded under a special
version of the standard Bream syntax expander, with an effort
to partially evaluate and reduce them to simpler forms as far
as is possible with the provided information.
Wirm-macros were partly inspired by the recursive macros offered by
Celoxica's now extinct Handel-C compiler.
APPROACHES NOT TAKEN
- Bream is NOT based on CSP-model ("Communicating Sequential Processes")
of Hoare, like for example, the commercial Impulse-C FPGA C compiler,
or Occam programming language for Transputers are.
After thinking about it for a while, I decided that the
CSP model is too coarse grained and limited for the applications
that I have in my mind, and devised my own straight-forward
function interface model instead.
However, nothing forbids the programmer of implementing a
CSP-framework of his own on the top of standard Bream,
utilizing the Block-RAMs of FPGA and FIFO-primitives
offered by Bream (as soon as they are available, I mean,
to be written, see above.)
- Wirm-macros do not offer any explicit ways for "staging",
i.e. to mark at what phase each computation is carried out.
After perusing a few papers by Taha, Kiselyov, et al,
I decided to go wholly opposite way, and let the Wirm-expansion
subsystem to automatically partially evaluate everything as
far as possible, without e.g. any explicit marking to distinguish
"compile-time variables/code" from "run-time variables/code".
The time will tell, whether this will lead to hopelessly
messy situations or not. Thus far the scope of Wirm-macros
should be strictly lexical, and no variable captures should be
possible. Also, we should guarantee that any expression
produces identical result when evaluated in FPGA
as when evaluated by wirm-expansion time.
LIMITATIONS, and TO DO FEATURES URGENTLY REQUIRED
Currently, at least the following features are urgently needed,
already for the intended basic (i.e. "Carpe") level of Bream:
- A function that inputs a byte from UART, for the speed 115200 bps
at least.
- Debounced starting button.
- Automatic start, without a button.
- Start based on character(s) read from UART.
- CAVEAT: Currently output-variables cannot be "fanned out",
i.e. they can be used only in one place in each module that has
them among their formal arguments. This is the fundamental
limitation in Verilog / FPGAs themselves, as although Bream-compiler
would happily compile such code, the Verilog-compiler of
a third party vendor will eventually give an error message such as
"ERROR:Xst:528 - Multi-source in Unit <test1out> on signal <txd>;
this signal is connected to multiple drivers."
This limitation makes e.g. printing multiple things
to a serial port an interesting exercise in convoluted code.
The situation will be improved after we have some kind
of PISO-interface to FIFOs (i.e. Block-RAMs of FPGA) on
whose other end the actual output routine will be located.
- A set of handy wrappers for FIFO and LIFO operations
(like dequeue, push, pop, whatever) that conform to
Bream FIM by their call interface.
Also versions for pipelined operations, with one push or pop
executed per clock cycle.
- A way to COMPLETELY UNROLL loops so that the final tail-recursive
loop back is eliminated from the produced code. The current
version of experimental let-unrolled syntax expander
in expsynta.scm unrolls only the specified fixed amount,
and then leaves the back-looping branches as they are.
(we would need also to determine which is the minimum safe factor
that each type of algorithm can be completely unrolled with.
E.g. for the minus-version of gcd, I reckon that it is
given by A072649, or maybe some multiple of it.)
- PIPELINING Function Interface Model, in addition to currently
implemented "regular" one.
That is, to obtain the full benefits of FPGA, it should be
possible to pipeline many functions together so that each elementary
operation takes just one clock cycle, and each function starts
computing the next answer immediately on the next cycle
(having passed the previous result to the next function
in the pipeline), without waiting for any specific starting
signal, and there should be some extra tag-bit wires through
the whole pipeline (which can involve also FIFOs!) indicating
when the pipeline has started (first valid data items)
and when does the final has passed through it.
It should be possible to embed pipelined functions
into a non-pipelined, "regular" matrix, so that a function
implementing the regular Bream FIM can start a pipeline,
and wait for its termination, and then return its result
to a surrounding form in completely regular fashion.
For the needs of pipelines, there should be a function like
(DELAY expr n_cycles), which would implement a "dummy subsection
for pipeline" delaying expression expr for n_cycles.
This would be for "padding" the faster (e.g. combinational)
branches of pipeline to stay in sync with the other,
noncombinational branches.
- Internal additions (mostly) to compile1.scm module
(a) Implement drop-comsig-and-returns-its-combcode in compile1.scm
correctly, so that we can produce less wire-cluttered
Verilog-code, easier to grasp and debug.
(Currently almost every subexpression results its own
wire-definition in the produced Verilog code.)
(b) There could be an abstraction for the state machines
as created by compiling handlers for <let-named>
and the (pseudocombinational) function definitions.
(This for the reasoning and optimization).
The current implementation outputs level-0 code
directly, and in some cases this will lead to
non-optimal results with unnecessary states.
(c) Way to associate extra tag-bits with any variable / data-wire
automatically by compiler (not just ready-wires) would be
very helpful for implementing:
- Pipelining (see above)
- Shared functions (see below)
- Run-time typing.
(d) Ways to specify "external" (or "semi-external") functions,
not just as static Verilog or Level0-code, but as
more flexible Level0-code, with access to type-resolved argument
widths, for implementing elegantly e.g. the DELAY function
mentioned above.
Moreover, currently, there is no any kind of SCHEDULING of resources,
and all the function invocations are essentially instantiated "in-line",
that is, all invocations of a particular function, even of the
identical type signatures, produce eventually onto a FPGA a separate
instance of associated state machine and other circuitry needed
to implement the said function.
Although in many cases this is _useful_ feature, implementing
parallelism by default, in some cases, to optimize the
for the area of FPGA, we would need
- A simple way to share the function instantation between
different callers ("SHARED FUNCTIONS") in time-multiplexed way.
This SHOULD be implemented for the next functionality level after
"Carpe".
- A kind of inverse situation for the above is where at one
call point we can multiplex to alternative functions,
i.e. "funargs" or function pointers.
This SHOULD be implemented for the next functionality level after
"Carpe".
- We need our OWN Scheme-interpreter for interpreting Bream-code,
with the type-fields of expressions natively supported, and with
the guarantee that any function evaluated in it will produce the
identical results (arithmetic-wise) as the same code would produce
if compiled to be run on FPGA.
However, we will not (probably) strive for cycle-accurate simulation.
Also, care should be taken that the "simulated" and "synthesizable"
versions of the language do not start diverging from each other
(e.g. that the latter would be just a tiny subset of the former).
Verilog itself provides a sad example of this.
- Clarify the semantics and roles of type-resolving and wirm-expansion
modules and especially their interplay.
Currently, it is the task of typeresolver
module, typreslv.scm, to spot both the invocations of external
functions as well as the wirm-macros, and it will postpone
the expansion of the latter ones, until the code so far
resolved/generated settles to a fixed point, after which it is
assumed that the settled widths can be used when expanding the
encountered wirm macros. This expansion in turn produces further code
that might need to be type-resolved, expanded, etc. recursively,
and the control can pass in mutual recursion many times between
type-resolver and wirm-expander modules.
(Currently, this in many cases takes embarrassingly high number
of passes, which is most likely just an artefact of my current sloppy
implementation).
- Clean the comments in source modules. In many cases there
are my long-winded speculations about the approaches
not actually taken, only ephemerally related to the current
code. Also, in some cases, the comments are in wholly
wrong places, after I have moved the commented function
to a different place or even a different module.
------------------------------------------------------------------------
DIRECTORY STRUCTURE for THIS RELEASE
bream/src -- Contains the Scheme source for Bream-compiler.
All the active modules are named with a traditional
8+3 characters fashion, with three-letter extension *.scm
standing for Scheme-source. The "main" module is named
toplevel.scm. See below, section MODULES, for a more
detailed explanation of each module.
bream/src/breamlib
-- Contains library of Bream functions available
for the programmer. These are currently named with an
extension .brm.scm, so as to indicate that they do not
contain standard Scheme-code, but instead, Bream-code.
Currently, the compiling function compile-topmodule
will always load all files of name *.brm.scm
from this directory, before it starts compiling
user's source.
HOWEVER, even although a function or wirm definition
were located in any of these files, there is no guarantee
that it would actually work as expected, or even
compile properly.
Those functions that have been already tested on FPGA
and have satisfied some basic tests, are marked as
such. The other definitions work as reserves of
"yet-to-be-checked and polished" material.
bream/src/breamlib/Verilog
-- Contains Verilog-modules (named *.v) for functions written
directly in Verilog.
These are e.g. for interfaces to I/O (for instance, UART)
or definitions which need a direct access to
system clock. For most of these the interfaces exhibit to the
caller side the regular Bream FIM (Function Interface Model),
although internally, they might implement the
starting and ready signals in some non-standard way.
bream/src/breamlib/Verilog_declares.brm.scm
-- This file contains declare-extfun declarations for the Verilog
routines contained in directory bream/src/breamlib/Verilog
so that user's bream-code can invoke them as they were
normal bream-coded functions.
bream/src/breamlib/Verilog/others
-- A directory for third party Verilog code, needed e.g. for
UART routines.
bream/src/restests
-- A set of syntax-only test suites intended to be run under the
bream-compiler, testing both the functionality of the
typeresolver (started with function run-typeresolve-testset)
and the syntax-expander (started with run-expsynta-testset).
Currently there are no similar procedures
for writing test-suites for the actual compiler itself
(e.g. either bream -> level-0 intermediate code
or bream -> Verilog code), but the programmer
has to peruse over the produced Verilog code by himself,
preferably with an alert mind and some experience in Verilog
coding.
bream/testprojects
-- A set of projects intended to test the functionality of
Bream-compiler itself and the correctness of the functions
provided in bream/src/breamlib/*.brm.scm
on the real world FPGA.
The main compiling routine compile-topmodule
expects that you have cd'd to this directory
(say (cd "~/bream/testprojects") + CTRL-X + CTRL-E
in MIT/GNU Scheme's REPL/*scheme* window)
and supposes that, in case the module name you gave
is "mytest1", that there is a subdirectory mytest1
which contains the main code in file named mytest1.brm.scm
The routine compile-topmodule reads that file in,
(together with all the library definitions from
bream/src/breamlib/*.brm.scm), and if everything
goes well (i.e. the type-resolver won't exit prematurely
because of type mismatch or too many passes),
will produce a Verilog module named mytest1.v in the same
directory, together with any additional needed
Verilog-modules.
------------------------------------------------------------------------
COMPILER MODULES
There are five major parts in Bream-compiler:
expsynta.scm -- Syntax expander. This rewrites/expands traditional special
forms of Scheme like "cond" (to a series of ifs), and "let"
(to lambda) but also some non-traditional, bream-specific
ones like "let-unrolled".
The syntax expander applies the rewriting-routine repeatedly
on the given source code, until it reaches a fixed point.
typreslv.scm -- Type resolver works as explained above, in section TYPING.
It also works by making changes until a fixed point is
reached.
expwirms.scm -- Syntax expander for recursive WIRM-macros. Uses also
the routines and handlers it "inherits" from expsynta.scm
Invoked by typreslv.scm (in a delayed fashion) for any
wirm-macros it encounters in code it tries to type-resolve.
compile1.scm -- The module to compile already type-resolved and wirm-expanded
bream-code to "level0"-intermediate code.
Among other things, this module implements the
regular Bream FIM (Function Interface Model),
takes care of the allocation of myriad of wires
generated (currently) for all the sub-expressions
and local variables, as well as for creating
appropriate finite state machines for the tail-recursive
loops.
lev0veri.scm -- Backend: Converts the "level0"-code produced by compile1.scm
to actual Verilog.
(We should also have lev0vhdl.scm !)
"Level0" is a simple S-expression based "wrapper formalism"
around Verilog, many of whose forms are based on Faulkner's
Verilisp language, available from:
http://home.comcast.net/~faulkner612/programming/verilisp/index.html
(sans Common Lisp macros that Verilisp provides).
Fundamentally, its function is just to provide semantically
transparent one-to-one mapping to a synthesizable subset of
Verilog, so that the functions in compile1.scm can output
their code as easily constructible S-expressions (using
backquotes and commas liberally), instead of being involved
with dirty I/O and having to compute the indentation levels
of the produced Verilog-code by itself
The above modules/subsystems try to be as independent and "orthogonal"
as possible in relation to each other, apart from the fact that
expwirms inherits from expsynta, and expwirms and typreslv
call each other in (somewhat muddled) mutual recursion, sharing
some members of their respective "context structures", rwc and trc.
However, at least the eventual compiling in compile1.scm is quite
independent on working of the other modules, expecting just to find
the type-annotations from the type-fields of the Bream source code
given as an argument to it, being entirely oblivious to a fact
whether those type-annotations were done by the programmer manually
or by the type-resolver automagically.
Additionally, there are following modules which contain
structure-definitions and functions required by above five.
dispatch.scm -- Generic dispatcher code, used by compile1, combopti, expsynta,
expwirms, lev0veri and typreslv.
toplevel.scm -- A toplevel module that should be loaded into MIT/GNU Scheme
by typing
(load "~/bream/src/toplevel.scm") and CTRL-X + CTRL-E
in MIT/GNU Scheme's REPL/*scheme* window.
(Also the code for running the test-suites is currently
located here.)
combopti.scm -- A minor module for optimizing combinational expressions
generated for the ready-expressions and (possible other?)
auxiliary wires.
Main entry point: (optimize-combinational-level0-code src coc)
readdefs.scm -- Reads definitions of bream-functions and wirm-macros from
the specified source files, and also the type signature
declarations (declare-extfun's) for any external Verilog
or VHDL-modules needed.
srcstrct.scm -- Definitions & accessors for "typed S-expr tree" structure.
(There are some inconsistencies in naming there. To be cleaned)
typesch1.scm -- Bit-masks and functions implementing the current
"Typing Scheme #1" used in Bream. I.e. specifies how
the "type" field in "texp"-structure defined in srcstrct.scm
is to be interpreted.
combfscm.scm -- A set of non-combinational functions offered by Bream
implemented in Scheme, for the needs of partial evaluation
done by expwirms.scm There are currently some deficiencies
here. (Also see section "own interpreter" in TO DO section).
funinfos.scm -- A definition for "funinfo"-structure, used for storing
the definitions of all bream functions and wirms
read in from the library modules.
utilits1.scm -- Miscellaneous utility functions.
------------------------------------------------------------------------