{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\"Chisel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Module 1: Introduction to Scala\n", "**Next: [Your First Chisel Module](2.1_first_module.ipynb)**\n", "\n", "## Motivation\n", "These tutorials use the *Jupyter* notebook environment so you can read code, make changes, and then run code snippets in place. We encourage you to experiment with tutorial code blocks to speed your way to Chisel mastery.\n", "\n", "In this first module, you will learn how to write basic Scala code and how to read more advanced Scala code." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Understanding Scala\n", "Scala is yet another programming language which supports common programming paradigms. We chose to use it for several reasons:\n", "\n", "- It is a good language for hosting an embedded DSL.\n", "- It has a powerful and elegant library for manipulating various collections of data.\n", "- It has a rigorous type system that helps catch a large class of errors very early in the development cycle, i.e. at compilation.\n", "- It has powerful ways of expressing and passing functions.\n", "- Chi*p*el, Chi*j*el, and Chi*c*el don't roll off the tongue as nicely as Chisel does.\n", "\n", "All of these points will become apparent as we talk about Chisel later, but for now, we are going to focus on the basics of reading and writing Scala code." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Variables and Constants - var and val\n", "Statements that create (mutable) variables and constant values (immutable variables) are preceded with the keywords *var* and *val* respectively. It is common practice to use *val* whenever possible. Why? Mostly to reduce the chances of re-using variables in ways that are error prone or make your code difficult to read. The structure of Scala makes this practice easier than you might expect.\n", "\n", "**Example:**
\n", "The subsequent code block is executable right here, within this notebook. To run it, focus on it by clicking it. When the cell is active, a box with a green bar on the left appears around it. Once selected, the active *code block* cell may be run using the **play** button found at the top of the notebook (highlighted in red below for reference). \n", "\n", "![](images/playbutton.png)\n", "\n", "Alternatively, you may use keyboard shortcuts. **Shift+enter** runs the currently active cell and moves the focus to the next cell. **Ctrl+enter** runs the current cell and keeps it in focus. \n", "\n", "Key examples begin with a blue and bold **Example**, while exercises begin with a red **Exercise**. Module 1 consists entirely of short examples, so we've omitted the example declaration for the rest of this module." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "var numberOfKittens = 6\n", "val kittensPerHouse = 101\n", "val alphabet = \"abcdefghijklmnopqrstuvwxyz\"\n", "var done = false" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One of the first things to notice is that unlike Java and C, Scala does not generally require semicolons at the end\n", "of statements. Scala infers the semicolon when there is a line feed. For instance, it can usually tell if a single statement is spread across multiple lines when the last thing on the line is an operater requiring additional code. The only time you need a semicolon is when you want to fit multiple statements onto one line.\n", "\n", "You use variables in obvious ways. The two `var`s may be reassigned to, while the two `val`s are immutable once created." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "numberOfKittens += 1\n", "\n", "// kittensPerHouse = kittensPerHouse * 2 // This would not compile; kittensPerHouse is not updatable\n", "\n", "println(alphabet)\n", "done = true" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Conditionals\n", "\n", "Scala implements conditionals like other programming languages." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// A simple conditional; by the way, this is a comment\n", "if (numberOfKittens > kittensPerHouse) { \n", " println(\"Too many kittens!!!\") \n", "}\n", "// The braces are not required when all branches are one liners. However, the \n", "// Scala Style Guide prefers brace omission only if an \"else\" clause is included.\n", "// (Preferably not this, even though it compiles...)\n", "if (numberOfKittens > kittensPerHouse) \n", " println(\"Too many kittens!!!\")\n", "\n", "// ifs have else clauses, of course\n", "// This is where you can omit braces!\n", "if (done) \n", " println(\"we are done\")\n", "else \n", " numberOfKittens += 1\n", "\n", "// And else ifs\n", "// For style, keep braces because not all branches are one liners. \n", "if (done) {\n", " println(\"we are done\")\n", "}\n", "else if (numberOfKittens < kittensPerHouse) {\n", " println(\"more kittens!\")\n", " numberOfKittens += 1\n", "}\n", "else {\n", " done = true\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But in Scala, an \"`if`\" conditional returns a value. What is that value? It's given by the last line of the selected branch. It's quite powerful, particularly when used to initialize values in functions and classes. It looks like:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "val likelyCharactersSet = if (alphabet.length == 26)\n", " \"english\"\n", "else \n", " \"not english\"\n", "\n", "println(likelyCharactersSet)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We created a constant *likelyCharactersSet* whose value is conditionally determined at runtime." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Methods (Functions)\n", "\n", "Methods are defined with the keyword `def`. Here, we'll abuse notation and also refer to them as functions. Function arguments (or parameters) are specified in a comma separated list that specifies the name of the argument, its type, and optionally a default value for it. Return types should be specified for clarity.\n", "\n", "Scala functions that do not have any arguments do not require empty parentheses. This often makes life easier for a developer in the situation where a member of a class becomes a function because there is some computation associated with referencing it. By convention, argument-less functions that do not have side effects (i.e. calling them does not change anything and they simply return a value) do not use parentheses, and functions that do have side effects (perhaps they change class variables or print stuff out) should require parentheses.\n", "\n", "## Simple Declarations" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// Simple scaling function with an input argument, e.g., times2(3) returns 6\n", "// Curly braces can be omitted for short one-line functions.\n", "def times2(x: Int): Int = 2 * x\n", "\n", "// More complicated function\n", "def distance(x: Int, y: Int, returnPositive: Boolean): Int = {\n", " val xy = x * y\n", " if (returnPositive) xy.abs else -xy.abs\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Overloading Functions\n", "The same function name can be used in more than one way. The parameters and their types determine a signature that allows the compiler to figure out which version of the function should be called. Overloaded functions should be avoided, though." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// Overloaded function\n", "def times2(x: Int): Int = 2 * x\n", "def times2(x: String): Int = 2 * x.toInt\n", "\n", "times2(5)\n", "times2(\"7\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Recursive and Nested Functions\n", "Curly braces define code scopes. Within a function's scope may exist more functions or recursive function calls. Functions defined in a certain scope are only accessible within that scope." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "/** Prints a triangle made of \"X\"s\n", " * This is another style of comment\n", " */\n", "def asciiTriangle(rows: Int) {\n", " \n", " // This is cute: multiplying \"X\" makes a string with many copies of \"X\"\n", " def printRow(columns: Int): Unit = println(\"X\" * columns)\n", " \n", " if(rows > 0) {\n", " printRow(rows)\n", " asciiTriangle(rows - 1) // Here is the recursive call\n", " }\n", "}\n", "\n", "// printRow(1) // This would not work, since we're calling printRow outside its scope\n", "asciiTriangle(6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Lists\n", "Scala implements a variety of aggregate or sequence objects. Lists are a lot like arrays but support additional operations for appending and extracting. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "val x = 7\n", "val y = 14\n", "val list1 = List(1, 2, 3)\n", "val list2 = x :: y :: y :: Nil // An alternate notation for assembling a list\n", "\n", "val list3 = list1 ++ list2 // Appends the second list to the first list\n", "val m = list2.length\n", "val s = list2.size\n", "\n", "val headOfList = list1.head // Gets the first element of the list\n", "val restOfList = list1.tail // Get a new list with first element removed\n", "\n", "val third = list1(2) // Gets the third element of a list (0-indexed)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# `for` Statement\n", "\n", "Scala has a `for` statement and it works like traditional for statements. You\n", "can iterate over a range of values." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for (i <- 0 to 7) { print(i + \" \") }\n", "println()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use `until` instead of `to` for iterating from 0 to 6 (7 is not included). " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for (i <- 0 until 7) { print(i + \" \") }\n", "println()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Add a `by` to increment by some fixed amount. The following prints out the even integers between 0 and 10." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for(i <- 0 to 10 by 2) { print(i + \" \") }\n", "println()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you have a collection of some kind and want to visit all of the elements, you can use `for`\n", "as an *iterator*, as in Java and Python. Here we make a list of 4 random integers and then sum them." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "val randomList = List(scala.util.Random.nextInt(), scala.util.Random.nextInt(), scala.util.Random.nextInt(), scala.util.Random.nextInt())\n", "var listSum = 0\n", "for (value <- randomList) {\n", " listSum += value\n", "}\n", "println(\"sum is \" + listSum)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scala's `for` has a lot more tricks it can do. It will work intuitively for\n", "a wide range of traditional iteration needs, but it may or may not be the most convenient thing to use. Operations like summing the elements of an array are often more easily done using a rich family of functions called *comprehensions* that are available across many different collections of elements. Later modules will provide more on `for` and its allies." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Reading Scala\n", "Being able to read Scala code and understand common naming conventions, design patterns, and best practices is an important step in becoming an effective Chisel designer. The potential for code reuse is one of the advantages of Chisel, but reuse is difficult if you can't read other people's code. Effectively parsing other people's code also makes it easier to seek out help, especially from resources like StackOverflow.\n", "\n", "The following sections show common code patterns you will see." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Packages and Imports \n", "\n", "```scala\n", "package mytools\n", "class Tool1 { ... }\n", "```\n", "\n", "When externally referencing code defined in a file containing the above lines, one should use:\n", " \n", "```scala\n", "import mytools.Tool1\n", "```\n", "\n", "Note: The package name **should** match the directory hierarchy. This is not mandatory, but failing to abide by this guideline can produce some unusual and difficult to diagnose problems. Package names by convention are lower case and do not contain separators like underscores. This sometimes makes good descriptive names difficult. One approach is to add a layer of hierarchy, e.g. `package good.tools`. Do your best. Chisel itself plays some games with the package names that do not conform to these rules.\n", "\n", "As shown above, `import` statements inform the compiler that you are using some additional libraries. Some common imports you will use when programming in Chisel are:\n", "\n", "```scala\n", "import chisel3._\n", "import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}\n", "```\n", "The first imports all the classes and methods in the chisel3 package; the underscore here works as a wildcard. The second imports specific classes from the chisel3.iotesters package." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Scala Is an Object Oriented Language\n", "Scala is object oriented, and it's important to understand a bit about this to take maximum advantage of both Scala and Chisel. Note that, without a doubt, there is more than one way to describe all this.\n", "1. Variables are objects.\n", "1. Constants in the sense of *Scala's* `val` declarative are also objects.\n", "1. Even literal values are objects.\n", "1. Even functions themselves are objects. More on this later.\n", "1. Objects are instances of classes.\n", " 1. In fact, in just about every way that matters in Scala, the *object* in *Objected Oriented* will be called an instance.\n", "1. In defining classes, the programmer specifies:\n", " 1. The data (`val`, `var`) associated with the class.\n", " 1. The operations, called methods or functions, that instances of the class can perform.\n", "1. Classes can extend other classes.\n", " 1. The class being extended is the superclass; the extendee is the subclass.\n", " 1. In this case, the subclass inherits the data and methods from the superclass.\n", " 1. There are many useful but controlled ways in which a class may extend or override inherited properties.\n", "1. Classes may inherit from traits. Think of traits as lightweight classes that allow specific, limited ways of inheriting from more than one superclass. \n", "1. (Singleton) Objects are a special kind of Scala class.\n", " 1. They are not objects as above. Remember, we're calling those instances.\n", "\n", "We are about to look at how to create a class in Scala." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# A Class Example\n", "An example of creating a Scala class might be" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// WrapCounter counts up to a max value based on a bit size\n", "class WrapCounter(counterBits: Int) {\n", "\n", " val max: Long = (1 << counterBits) - 1\n", " var counter = 0L\n", " \n", " def inc(): Long = {\n", " counter = counter + 1\n", " if (counter > max) {\n", " counter = 0\n", " }\n", " counter\n", " }\n", " println(s\"counter created with max value $max\")\n", "}\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What is here:\n", "* ```class WrapCounter``` -- This is the definition of **WrapCounter**.\n", "* ```(counterBits: Int)``` -- Creating a WrapCounter requires an integer parameter, nicely named to suggest it is the bit width of the counter.\n", "* Braces ({}) delimit a block of code. Most classes use a code block to define variables, constants, and methods (functions).\n", "* `val max: Long =` -- the class contains a member variable **max**, declared as type `Long` and initialized as the class is created.\n", "* `(1 << counterBits) - 1` computes the maximum value that can be contained in **counterBits** bits. Since **max** was created with `val` it cannot be changed.\n", "* A variable **counter** is created and initialized to **0L**. The **L** says that 0 is a long value; thus, **counter** is inferred to be Long.\n", "* **max** and **counter** are commonly called _member variables_ of the class.\n", "* A class method **inc** is defined which takes no arguments and returns a **Long** value.\n", "* The body of the method **inc** is a code block that has:\n", " * `counter = counter + 1` increments **counter**.\n", " * `if (counter > max) { counter = 0 }` tests if the counter is greater than the **max** value and sets it back to zero if it is.\n", " * `counter` -- The last line of the code block is important.\n", " * Any value expressed as the last line of a code block is considered to be the return value of that code block. The return value can be used or ignored by the calling statement.\n", " * This applies quite generally; for example, since an `if` then `else` statement defines its true and false clauses with code blocks, it can return a value i.e., `val result = if (10 * 10 > 90) \"greater\" else \"lesser\"` would create a `val` with the value \"greater\".\n", " * So in this case the function **inc** returns the value of **counter**.\n", "* `println(s\"counter created with max value $max\")` prints a string to standard output. Because the **println** is directly in the defining code block, it is part of the class initialization code and is run, i.e. prints out the string, every time an instance of this class is created.\n", "* The string printed in this case is an _interpolated_ string.\n", " * The leading **s** in front of the first double quote identifies this as an interpolated string.\n", " * An interpolated string is processed at run time. \n", " * The **\\$max** is replaced with the value of max.\n", " * If the **\\$** is followed by a code block, arbitrary Scala can be in that code block.\n", " * For example, **`println(s\"doubled max is ${max + max}\")`**.\n", " * The return value of this code block will be inserted in place of `${...}`.\n", " * If the return value is not a string, it will be converted to one; virtually every class or type in scala has an implicit conversion to a string defined).\n", " * You should generally avoid printing something every time an instance of a class is created to avoid flooding standard output, unless you're debugging." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Creating an Instance of a Class\n", "Let's use our example above to create a class. Scala instances are created via the built-in magic keyword `new`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "val x = new WrapCounter(2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may see instances being created without the keyword `new` i.e., `val y = WrapCounter(6)`.\n", "This occurs often enough to merit special attention, but requires the use of a companion object. It is described later.\n", "\n", "Example usage of the instance that has just been created is given next. (Try evaluating the cell below twice.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x.inc() // Increments the counter\n", "\n", "// Member variables of the instance x are visible to the outside, unless they are declared private\n", "if(x.counter == x.max) { \n", " println(\"counter is about to wrap\")\n", "}\n", "\n", "x inc() // Scala allows the dots to be omitted; this can be useful for making embedded DSL's look more natural" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Code Blocks\n", "Code blocks are delimited by braces. A block can contain zero or more lines of Scala code. The last line of Scala code becomes the return value (which may be ignored) of the code block. A code block with no lines would return a special null-like object called `Unit`. Code blocks are used throughout Scala: they are the bodies of class definitions, they form function and method definitions, they are the clauses of `if` statements, and they are the bodies of `for` and many other Scala operators.\n", "\n", "## Parameterized Code Blocks\n", "Code blocks can take parameters. In the case of class and method definitions, these parameters look like those in most conventional programming languages. In the example below, `c` and `s` are parameters of the code blocks." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "// A one-line code block doesn't need to be enclosed in {}\n", "def add1(c: Int): Int = c + 1\n", "\n", "class RepeatString(s: String) {\n", " val repeatedString = s + s\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**IMPORTANT**: There is another way in which code blocks may be parameterized. Here is an example." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "val intList = List(1, 2, 3)\n", "val stringList = intList.map { i =>\n", " i.toString\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The code block is passed to a method `map` of the class List. The `map` method requires that its code block have a single parameter. The code block is called for each member of the list, and the code block returns that member converted to a String. Scala is almost excessively accepting of variations of this syntax. You might see this written in many different ways. This type of code block is called an anonymous function, and more details on anonymous functions are provided in a later module." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The goal here is to help you recognize the different notational types when you encounter them. As you use Scala, these will seem more comfortable and familiar. Authors tend to gravitate to particular styles, and there are also individual syntactical situations in which one notation will seem more natural. One-liners tend to use the more concise forms. Complex blocks usually have a more narrative appearance. To make collaboration easier, one should consider skimming through best practices found in the [Scala Style Guide](http://docs.scala-lang.org/style/)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Named Parameters and Parameter Defaults\n", "Consider the following method definition.\n", "```scala\n", "def myMethod(count: Int, wrap: Boolean, wrapValue: Int = 24): Unit = { ... }\n", "```\n", "When calling the method, you will often see the parameter names along with the passed-in values.\n", "```scala\n", "myMethod(count = 10, wrap = false, wrapValue = 23)\n", "```\n", "Using named parameters, you can even call the function with a different ordering.\n", "```scala\n", "myMethod(wrapValue = 23, wrap = false, count = 10)\n", "```\n", "For frequently called methods, the parameter ordering may be obvious. But for less common methods and, in particular, boolean arguments, including the names with calls can make your code a lot more readable. If methods have a long list of arguments of the same type, using names also decreases the chance of error. Parameters to class definitions also use this named argument scheme (they are actually just the parameters to the constructor method for the class).\n", "\n", "When certain parameters have default values (that don't need to be overridden), callers only have to pass (by name) specific arguments that do not use defaults. Notice that the parameter `wrapValue` has a default value of 24. Therefore,\n", "\n", "```scala\n", "myMethod(wrap = false, count = 10)\n", "```\n", "will work as if 24 had been passed in." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# You're done!\n", "\n", "[Return to the top.](#top)" ] } ], "metadata": { "kernelspec": { "display_name": "Scala", "language": "scala", "name": "scala" }, "language_info": { "codemirror_mode": "text/x-scala", "file_extension": ".scala", "mimetype": "text/x-scala", "name": "scala", "nbconvert_exporter": "script", "version": "2.12.8" } }, "nbformat": 4, "nbformat_minor": 2 }