-
Notifications
You must be signed in to change notification settings - Fork 1
karttu/bream
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
 |  | |||
 |  | |||
 |  | |||
 |  | |||
Repository files navigation
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. ------------------------------------------------------------------------
About
Bream is a subset/dialect of Scheme that is compiled to run on an alternative platform. More details & initial sources will follow soon.
Topics
Resources
Stars
Watchers
Forks
Releases
No releases published
Packages 0
No packages published