Git Product home page Git Product logo

sylefeb / silice Goto Github PK

View Code? Open in Web Editor NEW
1.3K 42.0 78.0 53.3 MB

Silice is an easy-to-learn, powerful hardware description language, that simplifies designing hardware algorithms with parallelism and pipelines.

License: Other

CMake 1.10% Batchfile 0.05% Shell 1.33% C++ 51.98% Makefile 0.71% ANTLR 0.42% Roff 0.11% Verilog 9.33% HTML 7.24% CSS 0.16% C 24.03% Lua 1.70% PostScript 0.10% Tcl 0.34% Assembly 0.32% Python 1.02% Emacs Lisp 0.07% Pike 0.01% M4 0.01%
fpga programming language

silice's Introduction

Silice

A language for hardcoding algorithms with pipelines and parallelism into FPGA hardware


Quick links:

Important: Silice is under active development read more.

Important: To enjoy the latest features please use the draft branch. Read more about development branches.

Important: Something no longer compiles? The change log documents (rare) breaking changes and how to recover from them.


What is Silice?

Silice is an easy-to-learn, powerful hardware description language, that allows both to prototype ideas quickly and then refine designs to be compact and efficient.

Silice achieves this by offering a few, carefully designed high level design primitives atop a low level description language. In particular, Silice allows to write and combine algorithms, pipelines and per-cycle logic in a coherent, unified way. It features a powerful instantiation-time pre-processor, making it easy to describe parametric designs.

Silice offers a ready-to-go design environment, supporting many FPGA boards, both open-source and proprietary. It natively supports simulation and formal verification.

Silice syntax is simple, explicit and easy to read, and should feel familiar to C programmers and Verilog designers alike.

Silice comes with a ton of examples, from a simple blinky to SDRAM controllers, tiny RISCV CPUs (dual core RV32I in less than 120 lines!) and a GPU capable of rendering Doom and Quake levels on low cost, low power FPGAs. Examples include full hardware re-implementations of the render loops of Comanche 1992 and Doom 1993. Many basic components are available in the repository to get you started on your designs (VGA, HDMI, OLED, UART, SDRAM, SDCARD and SPIflash controllers).

The build system already supports many popular boards such as the IceBreaker, de10-nano, ULX3S, Fomu and IceStick. Silice works great with the open-source FPGA toolchain (yosys/nextpnr/icestorm), see our Ice40 and ECP5 examples.

You do not need an FPGA to start with Silice: designs and their outputs (e.g. VGA signals) can be simulated and visualized.

While I developed Silice for my own needs, I hope you'll find it useful for your projects!

Tutorial:

Checkout the Silice mega-tutorial (still being written).

A first example:

(see also the full blinky tutorial)

Code:
1  algorithm main(output uint8 led) {
2    uint28 counter = 0;      // a 28 bits unsigned integer
3    led := counter[20,8];    // LEDs updated every clock with the 8 most significant bits
4    while (1) {              // forever
5      counter = counter + 1; // increment counter
6    }
7  }
Compile:
cd projects
cd blinky
make mojov3
Enjoy!

First example in action on a Mojo v3

Explanations:

Line 1 is the entry point of any Silice hardware: the main algorithm. Line 2 we define a 28-bit unsigned int, initialized to 0. Initializers are mandatory and are always constants. Line 3 we request that the output led tracks the eight most significant bits of the counter variable. The syntax [20,8] means 8 bits wide starting from bit 20. The assignment to led uses the := operator which is an always assignment: led is now automatically updated with counter after each rising clock. Such assignments have to appear at the top of an algorithm, right before any other instruction.

Finally, lines 4-6 define the infinite loop that increments the counter. Of course the 28 bit counter will ultimately overflow and go back to 0, hence the cyclic LED light pattern. In this case, the loop takes exactly one cycle to execute: we have one increment per cycle at 50 MHz (the clock frequency of the Mojo v3).

We then compile with silice. The -f parameter indicates which framework to use: this is an FPGA platform dependent wrapper code. Here we are using the Mojo framework with LEDs only. Several other frameworks are provided, and it is easy to write your own.

The -o parameter indicates where to write the Verilog output. In this example we overwrite the main file of a pre-existing project, which is then compiled using Xilinx ISE toolchain. Fear not, we also have examples working with yosys, nextpnr and project icestorm!

Cycles and control flow:

Here is another small example outlining a core principle of Silice:

Code:
1 algorithm main() {
2    brom int12 sintbl[4096] = {...}
3    ...
4    while (1) { // render loop
5      // get cos/sin view
6      sintbl.addr = (viewangle) & 4095;
7  ++:
8      sinview     = sintbl.rdata;
9      sintbl.addr = (viewangle + 1024) & 4095;
10 ++:
11     cosview     = sintbl.rdata;
12     ...
Explanations:

This code is storing a sine table in a block ROM and accesses it to obtain a cosine and sine for the current view angle. Note the use of the ++: step operator in lines 7 and 10. This explicitly splits the exectution flow and introduces a one cycle delay, here waiting for the brom to output its result in field rdata for the select address in addr. Anything in between is considered combinational; for instance lines 8 and 9 are evaluated in parallel on hardware, as they each produce two pieces of independent circuitry.

Other examples with detailed explanations

This repo contains many example projects, some including detailed code walkthrough:

Getting started with Silice

See the getting started guide. Silice runs great on Windows, Linux, and macOS! To start writing code, see writing your first design. To see what can be done with Silice, check out our example projects (all are available in this repo).

Project status: in development

Silice can already be used to create non trivial designs, from a tiny Risc-V processor to an entire game render loop (visit the examples page).

However Silice is under active development. I decided to open the repo so everyone can join in the fun, but it is not yet stable and feature complete. Some documentation is lacking, some examples are outdated or far from polished, and known issues do exist (head out to the Issues page).

I am extremely attached to backward compatibility, and work very hard to avoid breaking changes. Some adjustments are however sometimes necessary. If something no longer works please check the change log, and feel free to reach out for help.

I hope you'll enjoy diving into Silice, and that you will find it useful. Please let me know your thoughts: comments and contributions are welcome!

Development branches

  • master is the latest, most stable version
  • wip is where new features are being implemented, less stable but reasonnable
  • draft is heavy experimental work in progress, likely unstable, may not compile

Directory structure

  • learn-silice contains documentation and tutorials
  • projects contains many demo projects (see README therein) as well as build scripts for several boards
  • bin contains the Silice binaries after compiling using the compile_silice_*.sh script
  • frameworks contains the frameworks for various boards and setups
  • tools contains tools useful for Silice development, either source or binary form (to be installed, see getting started)
  • src contains Silice core source code
  • antlr contains Silice grammar and parsing related source code
  • tests contains test scripts for Silice development

License

GPLv3 (Silice compiler) and MIT (examples and glue code), but please refer to the dedicated page.

Credits

  • Silice logo by Pierre-Alexandre Hugron (Twitter)

silice's People

Contributors

aaronferrucci avatar actions-user avatar cbalint13 avatar conversy avatar davidar avatar diadatp avatar emard avatar goran-mahovlic avatar mesabloo avatar osnr avatar resistor avatar rob-ng15 avatar sylefeb avatar tommythorn avatar torkos avatar trabucayre avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

silice's Issues

Shall we add the possibility to name parameters in calls?

Currently, upon calling an algorithm/subroutine/circuit the parameters are passed in order of input and received in order of outputs.
This can quickly be dangerous. We could (optionally or enforce) naming upon calls.

Instead of
(a,b) <- foo <- (c,d)
We'd have
(a=computed_value) <- foo <- (param_x=c,param_y=d)
With
suboutine foo(input uint8 param_x,input uint8 param_y,output uint8 computed_value) {}

outputs init value

Algorithm outputs take a value of 0 at startup. However, it might be desirable to set this default to something else. This also may be confusing when a variable with initial value is bound to the output. Clarify this behavior and implement an initial value.

Bitfields cannot be easily set from expressions

Bitfields can be used as initializers, but not in expressions. Should be easy enough as they translate to concatenations. Also, it seems assigning through a bitfield leads to an internal error.

Coder comfort improvements

Some comfort improvements:

  • [done] allow variable declaration within code (with block scope)
    => need to add per-block init

  • [done] preprocessor line markers ($$) have to be exactly on line start, relax (allow spaces)

  • [todo] allow if () {} else if () {} (without brackets on else of first if)

  • [done] allow whole group to be declared as input or output in algorithm parameters (equivalent to declaring all members as either input or output)

  • [done] relax declaration order, interleave vars, algos and subroutines

Don't install from package management if software/tool exists already

This tool looks really great!

A slight inconvenience during installation:
When running compile_silice_linux.sh, the installation of e.g. Verilator, yosys etc. is hardcoded and therefore always run.
The repo version of e.g. Verilator in my repo (Linux Mint) is quite old (3.916) but I have a more current version installed. It would be better if the script only installs these tools if the tool exists.
I have no idea how to achieve that and am aware that checking and comparing the version of each tool is a challenge in itself and not the scope of an Algorithm-to-Verilog converter.

Possibility to define views of packed bitfields

We often pack multiple entires in wide bitfields.

For instance
uint32 v = 32hffff0101;

We could define a 'pack' as

pack lo_hi = {
  int16   a;
  uint16 b;
}

and then

uint32 v = lo_hi(a=16hffff,b=16h0101);
lo_hi(v).a
lo_hi(v).b

The initializer uses names so we can later rearrange the pack without having to modify it.

Careful though that casting would require a temporary ... unless $signed() does the job?

NOTE: this relates to structs, but is a weaker form.

Locally declared variables

Finalize local (per-block) variables declaration to properly handle:

  • scope
  • init in block
    This should also work within a circuitry.

Update documentation.

Add support for structures

Add support for C-style structs, to build composed types.
This should not be too hard, a struct simply becomes an array of bits, each member has an index and bit-width.
Some important points:

  • we want nested structures
  • what to do with structures containing arrays?
  • if structures cannot contain arrays then we could specify layout (array of structs; vs struct of arrays)
  • initializers? (especially when nesting structures ...)
  • needs proper type checking!!

Wrong combinational cycle detection?

Triggers a cycle detection on sec_c_o , but this seems ok

                  tmp1_h    = (sec_c_o   * invd_h);
                  tmp2_m    = tmp1_m <<< $FPm$;
                  sec_c_o   = tmp2_m;
                  $macro_to_h('tmp1_h','c_o')$ // shift and round
                  if (btm > c_o) {
                    sec_c_o = tmp2_m + ((btm - c_o) * d_m); // offset texturing
                    c_o     = btm;
                  } else { if (top < c_o) {
                    sec_c_o = tmp2_m + ((top - c_o) * d_m); // offset texturing
                    c_o     = top;
                  } }

Compile time type checking

Add checks for bit-width and signed/unsigned mismatches (with either warning or error depending on command line flag). In particular detect the use of unsigned operators on signed quantities (e.g << versus <<<).

Verilator vga framework seems unstable

While it mostly works, there seems to be an instability in the verilator vga framework making it randomly crash in some cases. Could be uninitialized vars or out of bound access. To investigate and fix.

Allow bitfield to be used in variable and memory (bram*) declarations

The bitfield provides a definition of an uintN where N is the total width. For completeness and to avoid any hardcoded widths, it is important to be able to use the bitfield name in declarations. For example usage see:

input uint18 rdata, // NOTE, TODO: allow to use bitfield name (DrawColumn)

as well as:

// NOTE, TODO: cannot yet declare the bram with the bitfield
// bram DrawColumn columns[320] = {};
$$if HAS_COMPUTE_CLOCK then
dualport_bram uint18 columns<@clock,@sdram_clock>[320] = {};
$$else
dualport_bram uint18 columns[320] = {};
$$end

Dual block rams

We now have brams:
bram uint8 tbl[1024];

We also need dual clock block RAMs. Potential syntax:
dualbram uint8 tbl<@rclock1,@wclock2>[1024];

Expression binding <:: and output! can be misleading

Expression tracking may use the 'value at previous clock' assignment (::=). However, in some cases (tracking another tracker, or value bound to instance output) this does nothing as there is no flip-flop associated. In such a case ::= is the same as :=.

This should issue an error. The docummentation should explain and provide a solution on how to achieve the desired effect (i.e. adding an intermediate flip-flop).

Provide combinational depth insights

Related to but different from #5

It would be useful for the compiler to report a notion of computational depth, that is the longest combinational chain through dependencies (including through instances).

While this is far from actual hardware, it would give insights regarding the data paths and help visualize the flow of data and data overly long chains.

This relates to #5, since detecting combinational loops likely requires explicitly building the dependency chains.

Chaining subroutine calls

When chaining calls to subroutines it is possible to save one cycle:

(...a,b,c,...) <- sub1 <- (...);
(...) <- sub2 <- (...a,b,c,...);

Normally, there would be one cycle wasted in between due to return/setup params/call, but we could chain the calls by having sub1 directly jump to sub2. The trick is to modify the entry point of subroutines to have not only the 'standard' call but also context dependent calls that are determined at compile time. The call to sub2 would land in the state that directly pipes the output of sub1 used as input by sub2 (a,b,c).

This can be useful when writing code where subroutines are, in a sense, instructions (pieces of circuits to be reused multiple times), and the chain of subroutines describes a 'program' (sequence of instructions). This would make this type of coding more attractive.

Write custom parser for better syntax error reporting

Silice uses ANTLR, which was great to get started and focus on the essential.
Now, to produce a nice, useful syntax error reporting, a custom parser is important. As the language stabilizes this will become a must have.

Native support for inout

Like brams, inouts can become structs with three members (see sdramctrl.ice) generating a special module.

Code refactor

The Algorithm class has grown way too large. It consists of three main parts: gather / optimize / writer. These should be split in three classes interacting through a common datastructure with, possibly, a level of abstraction on the writer to target other HDLs than Verilog.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.