Skip to content
code0.HDL

Lexical elements

How bsc splits source text into tokens, and the exact spelling rules for identifiers, keywords, comments, integer, Real and string literals, and compiler directives.

In this chapter

  • Tell a parse error from a type error by reading which stage of bsc reported it
  • Choose legal identifiers, respecting the uppercase/lowercase rule and the keyword list
  • Read and write sized, unsized, fill, Real and string literals, including the width and escape rules
  • Use define, ifdef and the other directives as text substitution, with the parentheses a macro needs
Concepts in this chapter
  • Lexical elements
Prerequisite concepts

Before bsc parses anything, it splits the source characters into tokens: identifiers, keywords, literals, operators, and punctuation. Parsing, type checking, and elaboration all work on those tokens, so knowing the token rules tells you which stage an error message comes from. This chapter follows §2 of the BSV Language Reference Guide. You only need to read the module, rule, Reg, and $display code from A module and its interface and Registers and one rule; most examples here display a few values from a single rule and stop. One lexical element, the don't-care value ?, is taught where it is used, together with case, in Selection, case, and the don't-care value.

1. Identifiers and keywords

1.1 Identifier rules

An identifier names something: a variable, a module, a type. It consists of letters, digits, _, and $, and it cannot start with a digit. Case matters, so count and Count are different names.

Syntax

first character (letter, _, $) followed by letters, digits, _, $
Bit#(8) data_in;   // a valid identifier
Bit#(8) count2;    // digits are fine after the first character
Bit#(8) 2count;    // error: starts with a digit

Names beginning with $ are reserved for system tasks and functions such as $display and $finish. An instance name beginning with _ is left out of the hierarchical names in the generated Verilog.

1.2 What the first letter means

BSV uses the case of the first letter to tell kinds of names apart. This is grammar, not style: breaking it is a compile error.

First letterUsed forExamples
UppercasePackages, types, interface types, enum labels, tagged-union members, typeclassesTally, Bit, Reg, True
LowercaseVariables, modules, interface instances, rules, methods, other valuescount, mkTop, step
Bit#(4) Count = 4'd1;   // error: a variable starting with an uppercase letter
Bit#(4) count = 4'd1;   // correct

The first line fails with P0005, and the message says that an identifier starting with lowercase was expected.

1.3 Keywords

A keyword is a word the language reserves, so it can never be an identifier. module, rule, method, function, if, and case are keywords. BSV includes every SystemVerilog keyword. Keywords are lowercase except valueOf. The special type names Action and ActionValue cannot be redefined either.

Bit#(4) rule = 4'b1010;       // error: rule is a keyword
Bit#(4) ruleValue = 4'b1010;  // correct

No context turns a keyword back into a name. When rule appears where a variable name belongs, bsc reads it as the start of a new rule and fails.

1.4 Example: a keyword used as a variable name

This example fails to compile on purpose. Inside rule show, a local variable is named rule. Compilation stops in bsc, so no simulation runs.

Predict before you run. Does the failure come from type checking or from parsing? Does bsc ever reach the $display line?

Check

Where does KeywordAsIdentifier.bsv stop?

Your answer

KeywordAsIdentifier.bsv
package KeywordAsIdentifier;

(* synthesize *)
module mkTop(Empty);

  rule show;
    // rule is a keyword, so it cannot also name a variable.
    Bit#(4) rule = 4'b1010;
    $display("%b", rule);
    $finish(0);
  endrule

endmodule

endpackage

Check the result.

ItemDetail
Faulty codeThe keyword rule used as a local variable name
Failing stageParsing in bsc; type checking and simulation never run
MessageP0005 with Unexpected BSV 3.8 keyword `rule'
FixRename it to a lowercase non-keyword such as ruleValue

Try it yourself. Rename both occurrences to ruleValue. The file compiles and displays 1010.

2. Whitespace and comments

2.1 Whitespace

Spaces, tabs, newlines, form feeds, and carriage returns may appear freely between tokens. Whitespace only separates tokens; it never joins two pieces into one.

2.2 Line and block comments

Syntax

// a comment to the end of the line
/* a comment that may span lines */
Bit#(8) limit = 8'd5;  // upper bound
/* the rule below
   counts up to limit */
CommentEnds atOther comment markers inside it
//the newline/* and */ are ordinary text
/* ... */the first */// and a second /* are ordinary text

A comment behaves exactly like whitespace: it separates tokens and never splices them.

Note. Block comments do not nest. In /* a /* b */ c */, the first */ closes the comment, so c */ is left as source code and parsing fails with P0005. A block comment that is never closed fails with P0003.

2.3 Example: an identifier split by a comment

This example fails to compile on purpose. It puts a block comment in the middle of the name value: val/* gap */ue.

Predict before you run. Once the comment is gone, which tokens does bsc see: one identifier value, or two?

Check

Once the comment in val/* gap */ue is removed, what does bsc see?

Your answer

TokenCommentSplit.bsv
package TokenCommentSplit;

(* synthesize *)
module mkTop(Empty);
  Bit#(4) val/* gap */ue = 4'b1010;
endmodule

endpackage

Check the result.

ItemDetail
Faulty codeA block comment inside an identifier
Failing stageParsing in bsc
MessageP0005 with Unexpected identifier `ue'
FixMove the comment between complete tokens, as in value /* gap */ = ...

The comment turns into whitespace, leaving two identifiers, val and ue. A declaration cannot have a second identifier there, so the error points at ue. There is no special "comment inside a token" diagnostic. If P0005 names an unexpected identifier that looks like half a name, check for a comment or a space right before it.

3. Integer literals

3.1 Sized literals

A sized literal writes a width, an apostrophe, a base letter, and digits.

Syntax

<width>'<base><digits>
Base letterBaseExampleValue
d or Ddecimal8'd4242
b or Bbinary8'b1010_1010170
o or Ooctal6'o5242
h or Hhexadecimal16'hBEEF48879

Underscores between digits only help reading; they do not change the value. They are also allowed right after the base letter, so 16'h_BEEF equals 16'hBEEF.

3.2 Unsized literals

Leave out the width and you have an unsized literal: 125, -16, and 'h48454a are all unsized. The surrounding context decides the width.

Bit#(8) a = 125;     // context is Bit#(8), so the value is 8 bits
Int#(4) b = -8;      // the sign belongs to the literal: the minimum Int#(4)
Int#(4) c = 8;       // error: Int#(4) ranges from -8 to 7
KindSignInitial typeConverted to the context type by
Unsizedpart of the literalIntegerfromInteger from typeclass Literal
Sizednot part of the syntax; in -8'd1 the - is unary minusa bit vector of the stated widthfromSizedInteger from typeclass SizedLiteral

Int#(4) c = 8; fails with T0051, Literal 8 is not a valid Int#(4). The literal -8 includes its sign, so it is in range.

3.3 Fill literals

'0 and '1 are fill literals. They set every bit of whatever width the context needs to 0 or to 1.

Bit#(12) zeros = '0;   // all 12 bits zero
Bit#(5)  ones  = '1;   // all 5 bits one: 5'b11111

A fill literal has no width of its own. The same '1 produces a different number of bits under a different declared type.

3.4 Values too large for the width

When a literal's value does not fit, BSV does not drop the upper bits. Compilation fails instead.

CodeResult
Bit#(8) x = 8'd300;T0132, The value 300 does not fit in 8 bits.
Bit#(8) y = 300;T0051, Literal 300 is not a valid Bit#(8).

3.5 Example: printing integer literals

IntegerLiterals.bsv stores several literals in variables and displays them. Each output line begins with the literal's original spelling, so you can compare spelling and value side by side. Look for:

  • decimal and octal, written in two different bases;
  • hex and hexLeadingUnderscore, which differ only by an underscore;
  • zeros and ones, fill literals in a Bit#(12) context.

Predict before you run.

  1. Do 8'd42 and 6'o52 print the same number?
  2. What is 16'h_BEEF in decimal?
  3. How many hexadecimal digits does the '1 in Bit#(12) line show?

Check

How does the '1 in Bit#(12) line print?

Your answer

IntegerLiterals.bsv
package IntegerLiterals;

(* synthesize *)
module mkTop(Empty);

  // Sized literals carry their width and base in the literal itself.
  Bit#(8)  decimal = 8'd42;
  // Underscores between digits do not change the value.
  Bit#(8)  binary  = 8'b1010_1010;
  Bit#(16) hex     = 16'hBEEF;
  // An underscore immediately after a base marker is also ignored.
  Bit#(16) hexLeadingUnderscore = 16'h_BEEF;
  Bit#(6)  octal   = 6'o52;

  // An unsized literal starts as Integer and converts to the required type.
  Bit#(8) unsized = 125;

  // Fill literals set every bit of whatever width the context requires.
  Bit#(12) zeros = '0;
  Bit#(12) ones  = '1;

  rule show;
    $display("8'd42          = %0d", decimal);
    $display("8'b1010_1010   = %b", binary);
    $display("16'hBEEF       = %h", hex);
    $display("16'h_BEEF      = %0d", hexLeadingUnderscore);
    $display("6'o52          = %0d", octal);
    $display("unsized 125    = %0d", unsized);
    $display("'0 in Bit#(12) = %h", zeros);
    $display("'1 in Bit#(12) = %h", ones);
    $finish(0);
  endrule

endmodule

endpackage

Practice this concept

Use this concept in a short BSV problem as your next step.

Check the result.

Output lineWhat it shows
8'd42, 6'o52Both are 42; octal 52 is 5×8 + 2
8'b1010_1010Printed as 10101010, without the underscore
16'hBEEF, 16'h_BEEFThe same value; the second line prints it in decimal, 48879
unsized 125The unsized literal became 125 in a Bit#(8) context
'0 and '1 linesTwelve bits are three hex digits: 000 and fff

Try it yourself.

ChangeExpected result
Bit#(12) ones to Bit#(5) onesThe same '1 fills five bits and prints 1f
8'd42 to 8'd300The value exceeds 8 bits and compilation fails with T0132

4. Real literals

4.1 Spelling

A Real literal needs either digits after a decimal point or an exponent.

Syntax

<integer part>.<fraction digits>
<number>e<exponent>    // E also works; the exponent may have + or -
Real a = 1.2;      // decimal point form
Real b = 5e-3;     // exponent form, 0.005
Real c = 1_000.5;  // underscores may separate digits

A leading + or - is a unary operator, not part of the literal, and a literal cannot start with an underscore.

4.2 Rounding to binary64

Real literals are rounded to IEEE 754 binary64 (64-bit floating point). A value that is short in decimal may still have no exact binary form.

ValueIn binarybinary64 encoding
3.125exact (2 + 1 + 1/8)4009000000000000
0.005approximate3f747ae147ae147b

A Real literal has type Real by default. Types that provide fromReal through the RealLiteral typeclass, such as the library's FixedPoint, can accept it too.

Note. Real storage and general floating-point datapaths are not synthesized. Only Real constant expressions whose values are known at compile time are folded into bit values.

4.3 Example: two spellings of one value

RealLiterals.bsv writes each value once with a decimal point and once with an exponent, then compares them. $realtobits returns a Real value's binary64 encoding as a Bit#(64).

Predict before you run.

  1. Is 3.125 == 3125e-3 true?
  2. 0.005 has no exact binary form. Is 0.005 == 5e-3 still true?

Check

0.005 has no exact binary64 form. What does the comparison 0.005 == 5e-3 print?

Your answer

RealLiterals.bsv
package RealLiterals;

(* synthesize *)
module mkTop(Empty);

  // Compile-time Real constants can fold to IEEE 64-bit encodings without synthesizing general floating-point storage or datapaths.
  Real decimalA  = 3.125;
  Real exponentA = 3125e-3;
  Real decimalB  = 0.005;
  Real exponentB = 5e-3;

  rule show;
    $display("3.125 == 3125e-3: %0d; bits = %h (expected 4009000000000000)",
             decimalA == exponentA, $realtobits(decimalA));
    $display("0.005 == 5e-3 after IEEE rounding: %0d; bits = %h (expected 3f747ae147ae147b)",
             decimalB == exponentB, $realtobits(decimalB));
    $finish(0);
  endrule

endmodule

endpackage

Check the result.

Output lineWhat it shows
first lineComparison 1 and encoding 4009000000000000: an exactly representable value
second lineComparison 1 and encoding 3f747ae147ae147b: both spellings round to the same approximation

Both encodings are settled at compile time; the module contains no floating-point hardware.

Try it yourself. Replace the second pair with 0.5 and 5e-1. The comparison still prints 1, and the encoding becomes 3fe0000000000000. One half is a power of two, so it needs no rounding at all.

5. String literals

5.1 Spelling and escapes

A string literal is enclosed in double quotes and must end on the line where it starts. Characters you cannot type directly, such as a double quote or a newline, are written as escapes.

Syntax

"characters"
$display("plain");
$display("say \"hi\"\n");   // escaped quotes and a newline
EscapeCharacter
\nnewline
\thorizontal tab
\\backslash
\"double quote
\v, \f, \avertical tab, form feed, bell
\OOOexactly three octal digits, 000 to 377
\xHHexactly two hexadecimal digits, 00 to FF

Any other escape is an error. Even the familiar \r is not supported in BSV; it fails with P0091.

A string literal has type String by default, and types that provide fromString can accept one. Strings are for messages and compile-time work; a String is not a synthesizable byte array.

5.2 Example: decoding escapes

StringLiterals.bsv displays one kind of escape per line. Count characters after decoding, not characters in the source.

Predict before you run.

  1. What do "\102\123\126" and "\x42\x53\x56" spell? (Octal 102 is decimal 66.)
  2. How many characters does "\1024" contain?

Check

The octal then digit line holds an octal escape for the letter B followed by the digit 4. How many characters does it decode to?

Your answer

StringLiterals.bsv
package StringLiterals;

(* synthesize *)
module mkTop(Empty);

  rule show;
    // A string literal ends on the source line it starts on.
    $display("plain            = %s", "BSV");
    // Escapes put a quote or a backslash into the string.
    $display("quoted           = \"BSV\" \\");
    // Tab and newline are written as escapes, not as literal characters.
    $display("tabbed           = a\tb");
    // An octal escape takes three digits, a hexadecimal escape takes two.
    $display("octal escape     = %s", "\102\123\126");
    // An octal escape consumes exactly three digits; the trailing 4 is literal.
    $display("octal then digit = %s", "\1024");
    $display("hex escape       = %s", "\x42\x53\x56");
    $finish(0);
  endrule

endmodule

endpackage

Check the result.

Output lineWhat it shows
quoted\" and \\ print as a double quote and a backslash
tabbedA tab separates a and b
octal escape, hex escapeBoth spell BSV
octal then digitB4: only \102 is an escape, and 4 is an ordinary character

An octal escape consumes exactly three digits, so a digit after it is never ambiguous.

Try it yourself. Change "\1024" to "\1025". The B from the escape stays, and only the final character becomes 5.

5.3 Example: a short octal escape

This example fails to compile on purpose. The octal escape \10 has only two digits.

Predict before you run. Does bsc read \10 as the character with code 8, or reject it?

Check

The string in ShortOctalEscape.bsv has an octal escape with only two digits. What happens?

Your answer

ShortOctalEscape.bsv
package ShortOctalEscape;

(* synthesize *)
module mkTop(Empty);

  rule show;
    $display("%s", "\10");
    $finish(0);
  endrule

endmodule

endpackage

Check the result.

ItemDetail
Faulty codeThe octal escape \10 has two digits
Failing stageLexical analysis in bsc
MessageP0091 with Bad string escape char: `1'
FixPad to three digits, \010, which is the character with code 8

5.4 Example: a short hexadecimal escape

This example also fails to compile on purpose. Only one hexadecimal digit follows \x.

ShortHexEscape.bsv
package ShortHexEscape;

(* synthesize *)
module mkTop(Empty);
  rule show;
    $display("\x4");
  endrule
endmodule

endpackage

Check the result.

ItemDetail
Faulty codeThe hexadecimal escape \x4 has one digit
Failing stageLexical analysis in bsc
MessageP0091 with Bad string escape char: `x'
FixWrite two digits, \x04

Case does not matter in hexadecimal digits: \x4a and \x4A are the same character.

6. Compiler directives

A compiler directive starts with a backtick (`) and rewrites source text before normal parsing. It need not start in the first column or sit on a line by itself, but it is not recognized inside strings or comments.

6.1 Including files and adjusting locations

DirectiveEffect
`include "file"Inserts the file's contents at this point
`include <file>Inserts the file the same way as the quoted form
`line number "file" levelSets the file name and line number used by later diagnostics

`include paths are absolute or relative to the current directory, and the included file may contain directives too. The level of `line is 1 when entering an included file, 2 when returning, and 0 otherwise. `line is mostly used by external preprocessors that want diagnostics to point at the original source.

6.2 Defining and removing macros

Syntax

`define NAME replacement text
`define NAME(arg1, arg2) replacement text
`undef NAME
`define WIDTH 8
`define DOUBLE(v) ((v) + (v))

Bit#(`WIDTH) x = `DOUBLE(8'd6);   // Bit#(8) x = ((8'd6) + (8'd6));
ElementMeaning
`define NAME textStores the rest of the line as NAME; a trailing backslash continues it on the next line
`NAMEReplaced by the stored text, verbatim
Formal argumentsListed in parentheses right after the name and replaced by the actual argument text
`undef NAMERemoves the definition for the rest of the source
`resetallRemoves every current macro definition

Macros are untyped text substitution. A formal used twice copies the argument text twice, and names in the replacement can collide with names at the use site. Parenthesize the whole replacement and each formal in expression-like macros. For reusing arithmetic, a function with real types and scoping is safer.

Note. In a macro body, `" produces a double quote, `\`" a backslash and double quote, and `` joins the text on both sides with no space. `bluespec and `BLUESPEC are predefined.

Note. The BSV Language Reference Guide says commas inside strings, comments, and parentheses do not split actual arguments. With bsc 2026.01, though, commas inside strings and comments did split arguments and produced P0146; only commas inside parentheses stayed within one argument. Wrap an argument containing commas in parentheses, and check the behavior on the bsc version you use.

6.3 Conditional compilation

Syntax

`ifdef NAME
  // kept when NAME is defined
`elsif OTHER
  // kept when OTHER is defined
`else
  // kept when neither is defined
`endif

`ifndef keeps its first branch when the name is not defined. You may use any number of `elsif branches and at most one `else, and groups may nest. Only the first branch whose condition holds reaches the parser.

Note. Directives choose source text before compilation. They cannot read a register or switch behavior from cycle to cycle.

6.4 Example: macro substitution and a conditional branch

Directives.bsv defines a macro with a formal argument, DOUBLE, and a feature switch, USE_FEATURE. The declaration of selected depends on which `ifdef branch survives.

Predict before you run.

  1. What text does `DOUBLE(8'd6) become, and what is its value?
  2. Is selected a5 or 5a?

Check

With the define of USE_FEATURE in place, which value does selected get?

Your answer

Directives.bsv
package Directives;

// A macro with a formal argument. The invocation substitutes the actual text.
`define DOUBLE(value) ((value) + (value))
`define USE_FEATURE

(* synthesize *)
module mkTop(Empty);

  Bit#(8) doubled = `DOUBLE(8'd6);

  // Only the branch whose condition holds reaches the compiler.
`ifdef USE_FEATURE
  Bit#(8) selected = 8'hA5;
`else
  Bit#(8) selected = 8'h5A;
`endif

  rule show;
    $display("DOUBLE(8'd6) = %0d", doubled);
    $display("selected     = %h", selected);
    $finish(0);
  endrule

endmodule

endpackage

Check the result.

Output lineWhat it shows
DOUBLE(8'd6) = 12The macro became ((8'd6) + (8'd6)); the argument text appears twice
selected = a5USE_FEATURE is defined, so only the first `ifdef branch survived

Try it yourself.

ChangeExpected result
Delete the `define USE_FEATURE lineThe `else branch survives and selected becomes 5a
Replace DOUBLE with function Bit#(8) double(Bit#(8) v) = v + v; and call itStill 12, but now the argument type is checked and the body has its own scope

7. Common mistakes

7.1 Uppercase or keyword variable names

// Wrong
Bit#(4) Count = 4'd1;   // variable starting with an uppercase letter
Bit#(4) rule  = 4'd1;   // keyword

// Right
Bit#(4) count     = 4'd1;
Bit#(4) ruleValue = 4'd1;

Both wrong lines fail to parse with P0005. Variable names start with a lowercase letter and must not be keywords.

7.2 Nesting block comments

// Wrong: the first */ closes the comment
/* outer /* inner */ still outer */

// Right: use a line comment, or a single block comment
// outer /* inner */ still outer

Block comments do not nest, so the leftover text is read as code and parsing fails with P0005. To switch off a block of code temporarily, `ifdef is another option.

7.3 A literal too large for its width

// Wrong
Bit#(8) x = 8'd300;    // T0132

// Right: widen the type or fix the value
Bit#(9) x = 9'd300;

BSV never silently drops a literal's upper bits. Compare the value and width in the message and decide which one is wrong.

7.4 A macro without parentheses

// Wrong: `TWICE(8'd3) * 2 expands to 8'd3 + 8'd3 * 2, which is 9
`define TWICE(v) v + v

// Right: the same use expands to ((8'd3) + (8'd3)) * 2, which is 12
`define TWICE(v) ((v) + (v))

A macro knows nothing about operator precedence. Without parentheses, the expansion binds to the surrounding operators in unintended ways.

8. Summary

ElementKey point
IdentifiersLetters, digits, _, $, not starting with a digit; types start uppercase, values lowercase
KeywordsNever usable as identifiers, in any context
CommentsSeparate tokens like whitespace; block comments do not nest
Integer literals<width>'<base><digits>; unsized literals and '0/'1 take their width from context; an oversized value is a compile error
Real literalsRounded to binary64; enter hardware only as compile-time constants
StringsEnd on their line; octal escapes take three digits, hexadecimal escapes two
?The don't-care value, usable only where its value is never observed; see Selection, case, and the don't-care value
DirectivesText substitution before parsing; parenthesize expression macros

9. Check your understanding

  1. Why is val/* gap */ue not the identifier value?

    Show answer

    A comment acts as whitespace and separates tokens. Two identifiers remain, val and ue, and a declaration cannot have a second identifier there, so parsing fails with P0005.

  2. Are 16'o42 and 16'd42 the same value?

    Show answer

    No. 16'o42 is octal, 4×8 + 2 = 34, while 16'd42 is 42. Both are 16 bits wide.

  3. Int#(4) b = -8; compiles but Int#(4) c = 8; does not. Why?

    Show answer

    Int#(4) holds -8 to 7. In an unsized literal the sign is part of the literal, so -8 is in range, while 8 is not and fails with T0051.

  4. Explain how "\1024" becomes B4.

    Show answer

    An octal escape consumes exactly three digits, so \102 is decimal 66, the letter B. The remaining 4 is an ordinary character.

  5. With `define TWICE(v) v + v, the expression `TWICE(8'd3) * 2 is 9. How do you make it 12?

    Show answer

    Parenthesize the replacement and each formal: `define TWICE(v) ((v) + (v)). The use then expands to ((8'd3) + (8'd3)) * 2, which is 12. If you also want type checking, a function is the safer choice.

The Practice problems of this chapter follow the integer-literal example. In the first, four 16-bit constants all come out wrong; use the printed values to work out whether the base, the digits, or a fill literal is at fault. In the second, a mask meant to keep only the low four bits lets the whole input through.

Checkpoint

Run the example and compare it with your prediction

Sources

Licences and notices for the sources this site follows