Skip to content

Session 1: Introduction & Development Pipeline

Homework

Homework

  1. Install the course toolchain (Docker container)
  2. Run a “hello world” synthesis
  3. Verify your tools work before Thursday!

Words

AI-generated summary

This section was summarized using NotebookLM (Source: lecture page)

Verilog is a Hardware Description Language (HDL) used to design and verify digital circuits.

  • Role: It is used during the “RTL Design” phase of the chip development pipeline. Designers use it to describe digital logic, including combinational and sequential logic, as well as state machines.
  • Realization: Code written as “Synthesizable Verilog” can be converted into actual logic gate netlists using synthesis tools like Yosys.
  • Process: It serves as the starting point (Verilog Code) of the development flow. It goes through simulation (e.g., Icarus Verilog) and is eventually transformed into a physical layout (GDS file) on silicon.
  • Format: Verilog source files typically use the “.v” extension.

Verilogは、デジタル回路を設計し、その動作を検証するために使用されるハードウェア記述言語(HDL: Hardware Description Language)です。

  • 役割: チップ開発パイプラインの「RTL設計(RTL Design)」フェーズで使用されます。設計者はこれを用いて、組合せ回路、順序回路、状態マシンといったデジタル論理を記述します。
  • 実体化: 「合成可能なVerilog(Synthesizable Verilog)」として書かれたコードは、Yosysなどのツール(論理合成ツール)によって、実際の論理ゲートの接続情報へと変換されます。
  • 工程: チップ開発の出発点(Verilog Code)となり、シミュレーション(Icarus Verilog等)を経て、最終的にシリコン上のレイアウト(GDSファイル)へと自動変換されます。
  • 形式: 通常、ソースファイルには「.v」という拡張子が使われます。

(Integrated Infrastructure for Collaborative Open Source IC Tools)
IIC-OSIC-TOOLS is an integrated environment that combines a suite of open-source EDA (Electronic Design Automation) tools for semiconductor chip design, verification, and physical layout. It serves as an all-in-one toolset for beginners to experience the full chip development pipeline, spanning from initial design (Verilog) to manufacturing readiness (GDS).

By using this environment, designers can complete the entire “RTL-to-GDS flow”—the process of transforming Verilog code into final GDS manufacturing data—exclusively with open-source tools.

IIC-OSIC-TOOLSは、半導体チップの設計から検証、レイアウト作成までを行うためのオープンソースEDA(電子設計自動化)ツール一式をまとめた環境で、初心者が「設計(Verilog)から製造準備(GDS)」までのチップ開発パイプライン全体を体験するための一体型ツールセットです。
この環境を使用することで、設計者は「RTL-to-GDSフロー」(Verilogコードから最終的な製造用データであるGDSファイルを作成するまでの流れ)を、すべてオープンソースツールのみで完結させることができます

Homework 1

Homework

  1. Install the course toolchain (Docker container)

https://tools.futures.academany.org/login alt text

Instruction on Welcome page

alt text

About IIC-OSIC-TOOLS container:
- All tools run inside a Docker container — a self-contained Linux environment.
- A pre built all-in-one Docker image for analog and digital chip design.
- View all installed tools in this container

First try out?
- Open your tool’s server in the browser, enter the VNC password.
- Once inside, you should see your server desktop.
- Right click on the desktop and select “Open Terminal” to access the command line.
- Type: cd /foss/examples
- Run simulations with: make sim-fortune or make sim-all

Documenting and saving your work
Keep all your files in the following folder to make sure your work is saved during reboots.

/foss/designs/
During the course, you will need to document and share your work files. An easy way to document your work is through the “Editor” platoform we built:

Open Fabcloud Markdown Editor
Open this this is a web-based editor in a browser on your tools server, then you can edit your documentation website.

Start Container

alt text

alt text
alt text

alt text

Homework 2

Homework

2. Run a “hello world” synthesis

file structure: Click to open
/foss
├── designs
├── examples
│   ├── analog basics
│   ├── dice_roller
│   ├── fortune_teller
│   ├── lib
│   ├── Makefile
│   ├── morse_beacon
│   ├── pocket_synth
│   ├── ../foss/examples/QuickStart.md
│   ├── README.md
│   └── TROUBLESHOOTING.md
├── pdks
│   ├── ciel
│   ├── gf180mcuD
│   ├── ihp-sg13g2
│   └── sky130A
└── tools
    ├── bin
    ├── covered
    ├── cvc_rv
    ├── fpga
    ├── gaw3-xschem
    ├── gds3d
    ├── ghdl
    ├── gtkwave
    ├── irsim
    ├── iverilog
    ├── klayout
    ├── magic
    ├── netgen
    ├── ngspice
    ├── ngspyce
    ├── nvc
    ├── openems
    ├── openroad
    ├── openroad-librelane
    ├── openvaf
    ├── osic-multitool
    ├── padring
    ├── palace
    ├── pyopus
    ├── qflow
    ├── riscv-gnu-toolchain
    ├── rftoolkit
    ├── sak
    ├── slang
    ├── spicebind
    ├── surfer
    ├── veryl
    ├── verilator
    ├── xschem
    ├── xcircuit
    ├── xyce
    └── yosys

Get text from docker and use in PC

alt text
alt text alt text alt text git pullthe repo and edit in local

Practical way: Create a GitLab bookmark to save files from Docker container

From PC (Gitlab or local repo)

  1. In Gitlab or local repo, create new file in docs/foss/designs

    .
    ├── docs
    │   ├── foss
    │      └── designs
    │          └── bookmarks.html
    
  2. Copy and paste the following code to make bookmarks of:

    • Web Editor
    • Gitlab
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>Bookmarks</title>
    </head>
    <body>
      <h1>Bookmarks</h1>
    
      <p>
        <a href="https://editor.fabcloud.org/editor" target="_blank">
           Web Editor
        </a>
      </p>
    
      <p>
        <a href="https://gitlab.fabcloud.org/" target="_blank">
          GitLab
        </a>
      </p>
    
    </body>
    </html>
    
  3. save as bookmarks.html and push it to gitlab

  4. Go to Tool launcher and start container, then Open Browser VNC

In Docker Container

  1. Open browser and go to Gitlab https://gitlab.fabcloud.org/
  2. Find docs/foss/designs/bookmarks.html
  3. Copy the html code
  4. Open Terminal and go to /foss/designs/
  5. Create bookmarks.html and past the html code (and save)
    /foss/designs > nano bookmarks.html
    /foss/designs > ls
    bookmarks.html
    

Next time when open the docker

  1. Open File Manager
  2. Go to design folder and find bookmarks.html
  3. Double click to open the bookmarks.html, then go to Gitlab
  4. Now, any files in the Docker container can be uploaded to GitLab
    • Code created as assignments
    • Lecture files

Simulate the Fortune Teller project

Step 3: Run Your First Simulation

  1. make sim-fortune on Terminal in Docker
  2. Test complete alt text

What was happened here

/foss/examples > make sim-fortune
This command compiled and executed the Verilog simulation.

iverilog -Wall -g2012 -Ilib -o fortune_teller/fortune_teller.vvp fortune_teller/fortune_teller.v fortune_teller/fortune_teller_tb.v lib/debounce.v lib/uart_tx.v
First, Icarus Verilog (iverilog) compiled the following files:
  • fortune_teller.v (main module)
  • fortune_teller_tb.v (testbench)
  • library files such as debounce.v and uart_tx.v

It generated a simulation file (fortune_teller.vvp).

vvp fortune_teller/fortune_teller.vvp
The vvp runtime executed the simulation.

VCD info: dumpfile fortune_teller_tb.vcd opened for output.
Pressing button...
Cannot predict.

Pressing button again...
Yes definitely!

Test complete
fortune_teller/fortune_teller_tb.v:221: $finish called at 51055000000 (1ps)
During the simulation:
  • The testbench simulated pressing a button.
  • The circuit produced responses such as “Cannot predict.” and “Yes definitely!”
  • A waveform file (fortune_teller_tb.vcd) was generated.
  • The simulation ended when $finish was called at line 221 of the testbench.

View the Waveforms

Step 4: View the Waveforms

Note

gtkwave fortune_teller/fortune_teller_tb.vcd should be gtkwave fortune_teller_tb.vcd

  1. gtkwave fortune_teller_tb.vcd on Terminal in Docker
  2. alt text
  3. GTKwave is opened alt text

A window opens showing signals over time.  
Try:  
1. Click the + next to fortune_teller_tb in the left panel  
2. Select a signal (like clk or btn)  
3. Click “Append” to add it to the view  
4. Use the zoom buttons to see the waveform  
alt text
In this window, we can see the button being clicked by program of the chip.

Look at the Code

Step 6: Look at the Code

cat fortune_teller/fortune_teller.v | less
fortune_teller.v
// ============================================================================
// Fortune Teller - A Magic 8-Ball on a Chip
// ============================================================================
//
// HOW IT WORKS:
//   1. Press the button
//   2. Chip picks a random fortune from memory
//   3. Fortune appears on your computer's serial terminal
//
// WHAT YOU'LL LEARN:
//   - ROM (Read-Only Memory) - storing data in your chip
//   - LFSR (Linear Feedback Shift Register) - generating random numbers
//   - State machines - controlling the sequence of operations
//   - Using library modules - connecting pre-built components
//
// ============================================================================

`timescale 1ns/1ps

module fortune_teller #(
    // ========================================================================
    // Parameters
    // ========================================================================
    parameter CLK_FREQ = 50_000_000,  // Your board's clock speed (Hz)
    parameter BAUD     = 115200       // Serial communication speed
)(
    // ========================================================================
    // Ports
    // ========================================================================
    input  wire clk,    // Clock input from your board
    input  wire rst_n,  // Reset button (active LOW)
    input  wire btn,    // The "ask a question" button
    output wire tx,     // Serial output (connect to USB-serial adapter)
    output wire led     // Shows when chip is "thinking"
);

    // ========================================================================
    // Button Debouncing
    // ========================================================================
    // We use the debounce module from the library to clean up the button
    // signal. btn_pressed will pulse HIGH for exactly one clock cycle
    // when a valid button press is detected.

    wire btn_pressed;  // Clean, debounced button signal

    debounce #(
        .CLK_FREQ(CLK_FREQ)  // Pass our clock frequency to the debouncer
    ) debounce_inst (
        .clk(clk),
        .rst_n(rst_n),
        .btn_raw(btn),           // Raw button input
        .btn_pressed(btn_pressed) // Clean output
    );

    // ========================================================================
    // Fortune ROM (Read-Only Memory)
    // ========================================================================
    // We store 8 different fortunes in memory. Each fortune can be up to
    // 20 characters long. Characters are stored as ASCII codes.
    //
    // ASCII codes: 'A'=0x41, 'a'=0x61, ' '=0x20, '.'=0x2E, '\n'=0x0A, etc.
    // 0x00 marks the end of a string (null terminator).
    //
    // Total memory: 8 fortunes x 20 bytes = 160 bytes

    reg [7:0] rom [0:159];  // 160 bytes of ROM

    // Initialize the ROM with our fortunes
    // (In a real chip, this would be hardcoded during manufacturing)
    initial begin
        // Fortune 0: "Yes definitely!\n"
        rom[0]  = 8'h59;  // 'Y'
        rom[1]  = 8'h65;  // 'e'
        rom[2]  = 8'h73;  // 's'
        rom[3]  = 8'h20;  // ' '
        rom[4]  = 8'h64;  // 'd'
        rom[5]  = 8'h65;  // 'e'
        rom[6]  = 8'h66;  // 'f'
        rom[7]  = 8'h69;  // 'i'
        rom[8]  = 8'h6E;  // 'n'
        rom[9]  = 8'h69;  // 'i'
        rom[10] = 8'h74;  // 't'
        rom[11] = 8'h65;  // 'e'
        rom[12] = 8'h6C;  // 'l'
        rom[13] = 8'h79;  // 'y'
        rom[14] = 8'h21;  // '!'
        rom[15] = 8'h0A;  // '\n' (newline)
        rom[16] = 8'h00;  // End of string
        rom[17] = 8'h00;
        rom[18] = 8'h00;
        rom[19] = 8'h00;

        // Fortune 1: "Ask again later.\n"
        rom[20] = 8'h41;  // 'A'
        rom[21] = 8'h73;  // 's'
        rom[22] = 8'h6B;  // 'k'
        rom[23] = 8'h20;  // ' '
        rom[24] = 8'h61;  // 'a'
        rom[25] = 8'h67;  // 'g'
        rom[26] = 8'h61;  // 'a'
        rom[27] = 8'h69;  // 'i'
        rom[28] = 8'h6E;  // 'n'
        rom[29] = 8'h20;  // ' '
        rom[30] = 8'h6C;  // 'l'
        rom[31] = 8'h61;  // 'a'
        rom[32] = 8'h74;  // 't'
        rom[33] = 8'h65;  // 'e'
        rom[34] = 8'h72;  // 'r'
        rom[35] = 8'h2E;  // '.'
        rom[36] = 8'h0A;  // '\n'
        rom[37] = 8'h00;
        rom[38] = 8'h00;
        rom[39] = 8'h00;

        // Fortune 2: "Outlook not good.\n"
        rom[40] = 8'h4F;  // 'O'
        rom[41] = 8'h75;  // 'u'
        rom[42] = 8'h74;  // 't'
        rom[43] = 8'h6C;  // 'l'
        rom[44] = 8'h6F;  // 'o'
        rom[45] = 8'h6F;  // 'o'
        rom[46] = 8'h6B;  // 'k'
        rom[47] = 8'h20;  // ' '
        rom[48] = 8'h6E;  // 'n'
        rom[49] = 8'h6F;  // 'o'
        rom[50] = 8'h74;  // 't'
        rom[51] = 8'h20;  // ' '
        rom[52] = 8'h67;  // 'g'
        rom[53] = 8'h6F;  // 'o'
        rom[54] = 8'h6F;  // 'o'
        rom[55] = 8'h64;  // 'd'
        rom[56] = 8'h2E;  // '.'
        rom[57] = 8'h0A;  // '\n'
        rom[58] = 8'h00;
        rom[59] = 8'h00;

        // Fortune 3: "Signs point to yes\n"
        rom[60] = 8'h53;  // 'S'
        rom[61] = 8'h69;  // 'i'
        rom[62] = 8'h67;  // 'g'
        rom[63] = 8'h6E;  // 'n'
        rom[64] = 8'h73;  // 's'
        rom[65] = 8'h20;  // ' '
        rom[66] = 8'h70;  // 'p'
        rom[67] = 8'h6F;  // 'o'
        rom[68] = 8'h69;  // 'i'
        rom[69] = 8'h6E;  // 'n'
        rom[70] = 8'h74;  // 't'
        rom[71] = 8'h20;  // ' '
        rom[72] = 8'h74;  // 't'
        rom[73] = 8'h6F;  // 'o'
        rom[74] = 8'h20;  // ' '
        rom[75] = 8'h79;  // 'y'
        rom[76] = 8'h65;  // 'e'
        rom[77] = 8'h73;  // 's'
        rom[78] = 8'h0A;  // '\n'
        rom[79] = 8'h00;

        // Fortune 4: "Very doubtful.\n"
        rom[80] = 8'h56;  // 'V'
        rom[81] = 8'h65;  // 'e'
        rom[82] = 8'h72;  // 'r'
        rom[83] = 8'h79;  // 'y'
        rom[84] = 8'h20;  // ' '
        rom[85] = 8'h64;  // 'd'
        rom[86] = 8'h6F;  // 'o'
        rom[87] = 8'h75;  // 'u'
        rom[88] = 8'h62;  // 'b'
        rom[89] = 8'h74;  // 't'
        rom[90] = 8'h74;  // 't'
        rom[91] = 8'h75;  // 'u'
        rom[92] = 8'h6C;  // 'l'
        rom[93] = 8'h2E;  // '.'
        rom[94] = 8'h0A;  // '\n'
        rom[95] = 8'h00;
        rom[96] = 8'h00;
        rom[97] = 8'h00;
        rom[98] = 8'h00;
        rom[99] = 8'h00;

        // Fortune 5: "It is certain.\n"
        rom[100] = 8'h49;  // 'I'
        rom[101] = 8'h74;  // 't'
        rom[102] = 8'h20;  // ' '
        rom[103] = 8'h69;  // 'i'
        rom[104] = 8'h73;  // 's'
        rom[105] = 8'h20;  // ' '
        rom[106] = 8'h63;  // 'c'
        rom[107] = 8'h65;  // 'e'
        rom[108] = 8'h72;  // 'r'
        rom[109] = 8'h74;  // 't'
        rom[110] = 8'h61;  // 'a'
        rom[111] = 8'h69;  // 'i'
        rom[112] = 8'h6E;  // 'n'
        rom[113] = 8'h2E;  // '.'
        rom[114] = 8'h0A;  // '\n'
        rom[115] = 8'h00;
        rom[116] = 8'h00;
        rom[117] = 8'h00;
        rom[118] = 8'h00;
        rom[119] = 8'h00;

        // Fortune 6: "Reply hazy.\n"
        rom[120] = 8'h52;  // 'R'
        rom[121] = 8'h65;  // 'e'
        rom[122] = 8'h70;  // 'p'
        rom[123] = 8'h6C;  // 'l'
        rom[124] = 8'h79;  // 'y'
        rom[125] = 8'h20;  // ' '
        rom[126] = 8'h68;  // 'h'
        rom[127] = 8'h61;  // 'a'
        rom[128] = 8'h7A;  // 'z'
        rom[129] = 8'h79;  // 'y'
        rom[130] = 8'h2E;  // '.'
        rom[131] = 8'h0A;  // '\n'
        rom[132] = 8'h00;
        rom[133] = 8'h00;
        rom[134] = 8'h00;
        rom[135] = 8'h00;
        rom[136] = 8'h00;
        rom[137] = 8'h00;
        rom[138] = 8'h00;
        rom[139] = 8'h00;

        // Fortune 7: "Cannot predict.\n"
        rom[140] = 8'h43;  // 'C'
        rom[141] = 8'h61;  // 'a'
        rom[142] = 8'h6E;  // 'n'
        rom[143] = 8'h6E;  // 'n'
        rom[144] = 8'h6F;  // 'o'
        rom[145] = 8'h74;  // 't'
        rom[146] = 8'h20;  // ' '
        rom[147] = 8'h70;  // 'p'
        rom[148] = 8'h72;  // 'r'
        rom[149] = 8'h65;  // 'e'
        rom[150] = 8'h64;  // 'd'
        rom[151] = 8'h69;  // 'i'
        rom[152] = 8'h63;  // 'c'
        rom[153] = 8'h74;  // 't'
        rom[154] = 8'h2E;  // '.'
        rom[155] = 8'h0A;  // '\n'
        rom[156] = 8'h00;
        rom[157] = 8'h00;
        rom[158] = 8'h00;
        rom[159] = 8'h00;
    end

    // ========================================================================
    // LFSR - Random Number Generator
    // ========================================================================
    // An LFSR (Linear Feedback Shift Register) creates pseudo-random numbers
    // using XOR feedback. It cycles through all possible values (except 0)
    // before repeating.
    //
    // POLYNOMIAL THEORY:
    // The taps correspond to a primitive polynomial over GF(2):
    //   x^8 + x^5 + x^4 + x^3 + 1  (taps at bits 7,5,4,3 counting from 0)
    //
    // A primitive polynomial of degree n produces a sequence of length
    // 2^n - 1 before repeating. For n=8: 2^8 - 1 = 255 unique states.
    //
    // Not all tap combinations work! The polynomial must be "primitive"
    // (irreducible and of maximal period). These are tabulated in references
    // like Xilinx XAPP052 or Wikipedia "Linear-feedback shift register".
    //
    // RANDOMNESS TRICK:
    // The LFSR runs CONTINUOUSLY on every clock cycle. The exact moment
    // you press the button determines which "random" value you get.
    // Since humans can't time button presses to the nanosecond, this
    // gives effectively random results!

    reg [7:0] lfsr;  // 8-bit shift register

    // Feedback bit is XOR of bits 7, 5, 4, and 3
    wire lfsr_feedback = lfsr[7] ^ lfsr[5] ^ lfsr[4] ^ lfsr[3];

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            lfsr <= 8'hAC;  // Seed value (any non-zero value works)
        end
        else begin
            // Shift left and insert feedback bit at position 0
            lfsr <= {lfsr[6:0], lfsr_feedback};
        end
    end

    // ========================================================================
    // State Machine
    // ========================================================================
    // Controls the sequence: wait for button -> load char -> send char -> repeat

    // State definitions (2 bits = 4 possible states)
    localparam IDLE = 2'd0;  // Waiting for button press
    localparam LOAD = 2'd1;  // Loading a character from ROM
    localparam SEND = 2'd2;  // Sending character to UART
    localparam WAIT = 2'd3;  // Waiting for UART to finish

    // State machine registers
    reg [1:0] state;         // Current state
    reg [2:0] fortune_sel;   // Which fortune (0-7)
    reg [4:0] char_idx;      // Which character in the fortune (0-19)
    reg [7:0] current_char;  // The character we're currently sending
    reg       send_valid;    // Tell UART to send

    // Calculate ROM address: fortune_number * 20 + character_index
    wire [7:0] rom_addr = fortune_sel * 20 + char_idx;

    // UART ready signal (from the UART module)
    wire uart_ready;

    // ========================================================================
    // UART Transmitter Instance
    // ========================================================================
    // Connect to the UART module from the library

    uart_tx #(
        .CLK_FREQ(CLK_FREQ),
        .BAUD(BAUD)
    ) uart_inst (
        .clk(clk),
        .rst_n(rst_n),
        .data(current_char),   // Character to send
        .valid(send_valid),    // Start sending when HIGH
        .ready(uart_ready),    // UART tells us when it's ready
        .tx(tx)                // Serial output
    );

    // ========================================================================
    // LED Output
    // ========================================================================
    // LED is ON whenever we're not idle (i.e., while sending a fortune)

    assign led = (state != IDLE);

    // ========================================================================
    // Main State Machine Logic
    // ========================================================================

    always @(posedge clk or negedge rst_n) begin
        // --------------------------------------------------------------------
        // Reset
        // --------------------------------------------------------------------
        if (!rst_n) begin
            state        <= IDLE;
            fortune_sel  <= 0;
            char_idx     <= 0;
            send_valid   <= 0;
            current_char <= 0;
        end
        // --------------------------------------------------------------------
        // Normal Operation
        // --------------------------------------------------------------------
        else begin
            // Default: don't start a UART transmission
            send_valid <= 0;

            case (state)

                // ============================================================
                // IDLE: Wait for button press
                // ============================================================
                IDLE: begin
                    if (btn_pressed) begin
                        // Capture 3 bits from LFSR to pick fortune 0-7
                        fortune_sel <= lfsr[2:0];

                        // Start at first character
                        char_idx <= 0;

                        // Move to LOAD state
                        state <= LOAD;
                    end
                end

                // ============================================================
                // LOAD: Read character from ROM
                // ============================================================
                LOAD: begin
                    // Read the character at the current ROM address
                    current_char <= rom[rom_addr];

                    // Move to SEND state
                    state <= SEND;
                end

                // ============================================================
                // SEND: Send character to UART
                // ============================================================
                SEND: begin
                    // Check if we've reached the null terminator (end of string)
                    if (current_char == 0) begin
                        // Done! Go back to idle
                        state <= IDLE;
                    end
                    // Otherwise, wait for UART to be ready
                    else if (uart_ready) begin
                        // Start sending the character
                        send_valid <= 1;

                        // Move to WAIT state
                        state <= WAIT;
                    end
                end

                // ============================================================
                // WAIT: Wait for UART to start sending
                // ============================================================
                WAIT: begin
                    // When UART goes busy (ready drops), it has started
                    if (!uart_ready) begin
                        // Move to next character
                        char_idx <= char_idx + 1;

                        // Go load the next character
                        state <= LOAD;
                    end
                end

            endcase
        end
    end

endmodule

Make It Yours

Step 7: Make It Yours

  1. Copy an example to your designs folder:
    cp -r fortune_teller /foss/designs/my_fortune_teller
    cd /foss/designs/my_fortune_teller
    
  2. Edit the code:
    gedit fortune_teller.v &
    
  3. Change the fortunes (look for the ROM section around line 50)
my_fortune_teller
my_fortune_teller
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// ========================================================================
// Fortune ROM (Read-Only Memory)
// ========================================================================
// We store 5 different fortunes in memory. Each fortune can be up to
// 20 characters long. Characters are stored as ASCII codes.
//
// ASCII codes: 'A'=0x41, 'a'=0x61, ' '=0x20, '.'=0x2E, '\n'=0x0A, etc.
// 0x00 marks the end of a string (null terminator).
//
// Total memory: 5 fortunes x 20 bytes = 100 bytes

reg [7:0] rom [0:99];  // 100 bytes of ROM

// Initialize the ROM with our fortunes
// (In a real chip, this would be hardcoded during manufacturing)
initial begin
// Fortune 0: "Daikichi\n"
    rom[0]  = 8'h44;  // 'D'
    rom[1]  = 8'h61;  // 'a'
    rom[2]  = 8'h69;  // 'i'
    rom[3]  = 8'h6B;  // 'k'
    rom[4]  = 8'h69;  // 'i'
    rom[5]  = 8'h63;  // 'c'
    rom[6]  = 8'h68;  // 'h'
    rom[7]  = 8'h69;  // 'i'
    rom[8]  = 8'h0A;  // '\n'
    rom[9]  = 8'h00;  // End of string
    rom[10] = 8'h00;
    rom[11] = 8'h00;
    rom[12] = 8'h00;
    rom[13] = 8'h00;
    rom[14] = 8'h00;
    rom[15] = 8'h00;
    rom[16] = 8'h00;
    rom[17] = 8'h00;
    rom[18] = 8'h00;
    rom[19] = 8'h00;

    // Fortune 1: "Chukichi\n"
    rom[20] = 8'h43;  // 'C'
    rom[21] = 8'h68;  // 'h'
    rom[22] = 8'h75;  // 'u'
    rom[23] = 8'h6B;  // 'k'
    rom[24] = 8'h69;  // 'i'
    rom[25] = 8'h63;  // 'c'
    rom[26] = 8'h68;  // 'h'
    rom[27] = 8'h69;  // 'i'
    rom[28] = 8'h0A;  // '\n'
    rom[29] = 8'h00;  // End of string
    rom[30] = 8'h00;
    rom[31] = 8'h00;
    rom[32] = 8'h00;
    rom[33] = 8'h00;
    rom[34] = 8'h00;
    rom[35] = 8'h00;
    rom[36] = 8'h00;
    rom[37] = 8'h00;
    rom[38] = 8'h00;
    rom[39] = 8'h00;

    // Fortune 2: "Shokichi\n"
    rom[40] = 8'h53;  // 'S'
    rom[41] = 8'h68;  // 'h'
    rom[42] = 8'h6F;  // 'o'
    rom[43] = 8'h6B;  // 'k'
    rom[44] = 8'h69;  // 'i'
    rom[45] = 8'h63;  // 'c'
    rom[46] = 8'h68;  // 'h'
    rom[47] = 8'h69;  // 'i'
    rom[48] = 8'h0A;  // '\n'
    rom[49] = 8'h00;  // End of string
    rom[50] = 8'h00;
    rom[51] = 8'h00;
    rom[52] = 8'h00;
    rom[53] = 8'h00;
    rom[54] = 8'h00;
    rom[55] = 8'h00;
    rom[56] = 8'h00;
    rom[57] = 8'h00;
    rom[58] = 8'h00;
    rom[59] = 8'h00;

    // Fortune 3: "Kichi\n"
    rom[60] = 8'h4B;  // 'K'
    rom[61] = 8'h69;  // 'i'
    rom[62] = 8'h63;  // 'c'
    rom[63] = 8'h68;  // 'h'
    rom[64] = 8'h69;  // 'i'
    rom[65] = 8'h0A;  // '\n'
    rom[66] = 8'h00;  // End of string
    rom[67] = 8'h00;
    rom[68] = 8'h00;
    rom[69] = 8'h00;
    rom[70] = 8'h00;
    rom[71] = 8'h00;
    rom[72] = 8'h00;
    rom[73] = 8'h00;
    rom[74] = 8'h00;
    rom[75] = 8'h00;
    rom[76] = 8'h00;
    rom[77] = 8'h00;
    rom[78] = 8'h00;
    rom[79] = 8'h00;

    // Fortune 4: "Kyo\n"
    rom[80] = 8'h4B;  // 'K'
    rom[81] = 8'h79;  // 'y'
    rom[82] = 8'h6F;  // 'o'
    rom[83] = 8'h0A;  // '\n'
    rom[84] = 8'h00;  // End
    rom[85] = 8'h00;
    rom[86] = 8'h00;
    rom[87] = 8'h00;
    rom[88] = 8'h00;
    rom[89] = 8'h00;
    rom[90] = 8'h00;
    rom[91] = 8'h00;
    rom[92] = 8'h00;
    rom[93] = 8'h00;
    rom[94] = 8'h00;
    rom[95] = 8'h00;
    rom[96] = 8'h00;
    rom[97] = 8'h00;
    rom[98] = 8'h00;
    rom[99] = 8'h00;
end

Test your changes:

Example

iverilog -Wall -g2012 -I/foss/examples/lib -o test.vvp \
    fortune_teller.v fortune_teller_tb.v \
    /foss/examples/lib/debounce.v /foss/examples/lib/uart_tx.v
vvp test.vvp

In my docker

iverilog -Wall -g2012 -I/foss/examples/lib -o test.vvp \
    my_fortune_teller.v my_fortune_teller_tb.v \
    /foss/designs/lib/debounce.v /foss/designs/lib/uart_tx.v
vvp test.vvp
cp -r Makefile /fos/designs/
cd ../designs
ls
Makefile my_fortune_teller
cd Makefile

Edit Makefile
fortune_teller -> my_fortune_teller

Makefile
Makefile
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# =============================================================================
# Fab Futures Examples - Build Automation
# =============================================================================
#
# Usage:
#   make sim-fortune    - Simulate Fortune Teller
#   make sim-synth      - Simulate Pocket Synth
#   make sim-dice       - Simulate Dice Roller
#   make sim-led        - Simulate Morse Beacon
#   make sim-all        - Simulate all projects
#   make lint-all       - Lint all projects
#   make clean          - Remove generated files
#
# =============================================================================

# Default shell
SHELL := /bin/bash

# Tools
IVERILOG := iverilog
VVP := vvp
VERILATOR := verilator
YOSYS := yosys

# Common options
IVERILOG_FLAGS := -Wall -g2012
VERILATOR_FLAGS := --lint-only -Wall

# Library path
LIB := lib

# =============================================================================
# Simulation targets
# =============================================================================

.PHONY: sim-fortune sim-synth sim-dice sim-led sim-all

sim-fortune: my_fortune_teller/my_fortune_teller.vvp
    $(VVP) $<

sim-synth: pocket_synth/pocket_synth.vvp
    $(VVP) $<

sim-dice: dice_roller/dice_roller.vvp
    $(VVP) $<

sim-led: morse_beacon/morse_beacon.vvp
    $(VVP) $<

sim-all: sim-fortune sim-synth sim-dice sim-led
    @echo "All simulations complete."

# =============================================================================
# Compilation targets
# =============================================================================

my_fortune_teller/my_fortune_teller.vvp: my_fortune_teller/my_fortune_teller.v my_fortune_teller/my_fortune_teller_tb.v $(LIB)/debounce.v $(LIB)/uart_tx.v
    $(IVERILOG) $(IVERILOG_FLAGS) -I$(LIB) -o $@ $^

pocket_synth/pocket_synth.vvp: pocket_synth/pocket_synth.v pocket_synth/pocket_synth_tb.v $(LIB)/debounce.v
    $(IVERILOG) $(IVERILOG_FLAGS) -I$(LIB) -o $@ $^

dice_roller/dice_roller.vvp: dice_roller/dice_roller.v dice_roller/dice_roller_tb.v $(LIB)/debounce.v $(LIB)/uart_tx.v
    $(IVERILOG) $(IVERILOG_FLAGS) -I$(LIB) -o $@ $^

morse_beacon/morse_beacon.vvp: morse_beacon/morse_beacon.v morse_beacon/morse_beacon_tb.v $(LIB)/debounce.v
    $(IVERILOG) $(IVERILOG_FLAGS) -I$(LIB) -o $@ $^

# =============================================================================
# Lint targets
# =============================================================================

.PHONY: lint-fortune lint-synth lint-dice lint-led lint-all

lint-fortune:
    $(VERILATOR) $(VERILATOR_FLAGS) -I$(LIB) my_fortune_teller/my_fortune_teller.v $(LIB)/debounce.v $(LIB)/uart_tx.v

lint-synth:
    $(VERILATOR) $(VERILATOR_FLAGS) -I$(LIB) pocket_synth/pocket_synth.v $(LIB)/debounce.v

lint-dice:
    $(VERILATOR) $(VERILATOR_FLAGS) -I$(LIB) dice_roller/dice_roller.v $(LIB)/debounce.v $(LIB)/uart_tx.v

lint-led:
    $(VERILATOR) $(VERILATOR_FLAGS) -I$(LIB) morse_beacon/morse_beacon.v $(LIB)/debounce.v

lint-all: lint-fortune lint-synth lint-dice lint-led
    @echo "All lint checks passed."

# =============================================================================
# Synthesis (Yosys) - produces gate count estimate
# =============================================================================

.PHONY: synth-fortune synth-synth synth-dice synth-led

synth-fortune:
    $(YOSYS) -p " \
        read_verilog -I$(LIB) my_fortune_teller/my_fortune_teller.v $(LIB)/debounce.v $(LIB)/uart_tx.v; \
        synth -top my_fortune_teller; \
        stat"

synth-synth:
    $(YOSYS) -p " \
        read_verilog -I$(LIB) pocket_synth/pocket_synth.v $(LIB)/debounce.v; \
        synth -top pocket_synth; \
        stat"

synth-dice:
    $(YOSYS) -p " \
        read_verilog -I$(LIB) dice_roller/dice_roller.v $(LIB)/debounce.v $(LIB)/uart_tx.v; \
        synth -top dice_roller; \
        stat"

synth-led:
    $(YOSYS) -p " \
        read_verilog -I$(LIB) morse_beacon/morse_beacon.v $(LIB)/debounce.v; \
        synth -top morse_beacon; \
        stat"

# =============================================================================
# Clean
# =============================================================================

.PHONY: clean

clean:
    rm -f */*.vvp */*.vcd
    @echo "Cleaned generated files."

# =============================================================================
# Help
# =============================================================================

.PHONY: help

help:
    @echo "Fab Futures Examples Makefile"
    @echo ""
    @echo "Simulation:"
    @echo "  make sim-fortune  - Run Fortune Teller simulation"
    @echo "  make sim-synth    - Run Pocket Synth simulation"
    @echo "  make sim-dice     - Run Dice Roller simulation"
    @echo "  make sim-led      - Run Morse Beacon simulation"
    @echo "  make sim-all      - Run all simulations"
    @echo ""
    @echo "Linting:"
    @echo "  make lint-all     - Lint all projects with Verilator"
    @echo ""
    @echo "Synthesis:"
    @echo "  make synth-fortune - Synthesize and show gate count"
    @echo ""
    @echo "Cleanup:"
    @echo "  make clean        - Remove generated files"

File structure

.
├── designs
│   ├── Makefile
│   ├── lib
│   └── my_fortune_teller
│       ├── my_fortune_teller_tb.v
│       ├── my_fortune_teller.v
│       └── my_fortune_teller.vvp
cd foss/designs
make sim-fortune

/foss/designs > make sim-fortune
vvp my_fortune_teller/my_fortune_teller.vvp
VCD info: dumpfile fortune_teller_tb.vcd opened for output.
Pressing button...

Pressing button again...

Test complete
my_fortune_teller/my_fortune_teller_tb.v:221: $finish called at 51055000000 (1ps)

What Do These Files Mean?

fortune_teller/
├── fortune_teller.v      # The actual chip design (Verilog)
└── fortune_teller_tb.v   # Test code that 
simulates pressing buttons
  • .v files = Verilog source code (what becomes your chip)
  • _tb.v files = Testbench (simulates the outside world)
  • .vcd files = Waveform data (created when you simulate)
  • .vvp files = Compiled simulation (temporary, can delete)