Skip to content

Final Project > Production

As I learn more and understand more, my idea for the Final Project is also evolving. Here’s the timeline:

  • Learning about the Moore Machine and State Case logic
  • Turning ON and OFF an LED with a Button using State Case logic
  • Playing a tone with a Button using State Case logic
  • Playing a musical chord with a Button using State Case logic

…now I want to see if I can replicate my HiChord synthesizer functionality as a project.

7 Buttons that trigger diatonic Chords using State Case logic

Hi-Chord Synthesizer

The HiChord synthesizer has 7 keys that each play diatonic scale chords, instead of individual notes. The Diatonic scale in C:

  • C major > C E G
  • D min > D F A
  • E min > E G B
  • F major > F A C
  • G major > G B D
  • A min > A C E
  • B dim > B D F

Final Project > 7-button Diatonic Chord Monophonic Synthesizer Chip

During the Final Project Development phase, a 1-button Chord Generator with Prescaler code was vibe-coded with ChatGPT’s help.

// Button-Chord State Machine - Clock Prescaler
module button_chord_FSM (
    input  clk,
    input  reset,
    input  ON,
    input  OFF,
    output reg audio_out
);

    //==================================================
    // State machine
    //==================================================
    reg state;

    localparam OFF_STATE = 1'b0;
    localparam ON_STATE  = 1'b1;

    always @(posedge clk or posedge reset) begin
        if (reset)
            state <= OFF_STATE;
        else begin
            case (state)
                OFF_STATE: begin
                    if (ON)
                        state <= ON_STATE;
                    else
                        state <= OFF_STATE;
                end

                ON_STATE: begin
                    if (OFF)
                        state <= OFF_STATE;
                    else
                        state <= ON_STATE;
                end
            endcase
        end
    end

    //==================================================
    // Prescaler: 50 MHz -> 1 MHz tick
    // 50 clock cycles of 50 MHz = 1 us
    // so make a pulse every 50 cycles
    //==================================================
    reg [5:0] prescaler;
    reg audio_tick;

    always @(posedge clk or posedge reset) begin
        if (reset) begin
            prescaler  <= 6'd0;
            audio_tick <= 1'b0;
        end else begin
            if (prescaler == 6'd49) begin
                prescaler  <= 6'd0;
                audio_tick <= 1'b1;   // one-clock pulse
            end else begin
                prescaler  <= prescaler + 6'd1;
                audio_tick <= 1'b0;
            end
        end
    end

    //==================================================
    // Chord generator
    // Using 1 MHz tick so 16-bit counters are enough
    //
    // divider = 1,000,000 / (2 * f_note)
    //
    // C3 = 130.81 Hz -> ~3822
    // E3 = 164.81 Hz -> ~3034
    // G3 = 196.00 Hz -> ~2551
    //==================================================
    reg [15:0] counter1, counter2, counter3;
    reg tone1, tone2, tone3;

    localparam [15:0] C3_DIV = 16'd3822;
    localparam [15:0] E3_DIV = 16'd3034;
    localparam [15:0] G3_DIV = 16'd2551;

    always @(posedge clk or posedge reset) begin
        if (reset) begin
            counter1  <= 16'd0;
            counter2  <= 16'd0;
            counter3  <= 16'd0;
            tone1     <= 1'b0;
            tone2     <= 1'b0;
            tone3     <= 1'b0;
            audio_out <= 1'b0;
        end else begin
            if (state == ON_STATE) begin

                if (audio_tick) begin
                    // Tone 1: C3
                    if (counter1 == C3_DIV) begin
                        counter1 <= 16'd0;
                        tone1    <= ~tone1;
                    end else begin
                        counter1 <= counter1 + 16'd1;
                    end

                    // Tone 2: E3
                    if (counter2 == E3_DIV) begin
                        counter2 <= 16'd0;
                        tone2    <= ~tone2;
                    end else begin
                        counter2 <= counter2 + 16'd1;
                    end

                    // Tone 3: G3
                    if (counter3 == G3_DIV) begin
                        counter3 <= 16'd0;
                        tone3    <= ~tone3;
                    end else begin
                        counter3 <= counter3 + 16'd1;
                    end

                    // combine square waves
                    audio_out <= tone1 ^ tone2 ^ tone3;
                end
            end else begin
                counter1  <= 16'd0;
                counter2  <= 16'd0;
                counter3  <= 16'd0;
                tone1     <= 1'b0;
                tone2     <= 1'b0;
                tone3     <= 1'b0;
                audio_out <= 1'b0;
            end
        end
    end

endmodule

From 1 to 7 Buttons and Chords

My thinking is, if I can generate one chord with one button, it should not be heroic to make 7 chords with 7 buttons. What I think I will need to do:

  1. Add 4 more counters, 4 more tone registers and 4 more tone cases, for the other 4 tones in the scale
  2. Add explicit inputs for each button
  3. Add conditional statements to determine which button is being pressed and which tone state cases to activate.
// 7-Button Diatonic Chord Monophonic Synthesizer Chip  
// coded with ChatGPT assistance  

module button_chord_7_optimized (
    input  clk,
    input  reset,

    //unique input for each of 7 chord button
    input  BTN_C,   // C major = C E G
    input  BTN_D,   // D minor = D F A
    input  BTN_E,   // E minor = E G B
    input  BTN_F,   // F major = F A C
    input  BTN_G,   // G major = G B D
    input  BTN_A,   // A minor = A C E
    input  BTN_B,   // B dim   = B D F

    output reg audio_out //assigned to always block 
);

    // Clock Prescaler: 50 MHz -> 1 MHz 
    reg [5:0] prescaler; //6-bit register 
    reg audio_tick; 

    // Prescaler Logic block 
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            prescaler  <= 6'd0;
            audio_tick <= 1'b0;
        end else begin
            if (prescaler == 6'd49) begin //count to 50
                prescaler  <= 6'd0;
                audio_tick <= 1'b1; //generate one audio tick
            end else begin
                prescaler  <= prescaler + 6'd1;
                audio_tick <= 1'b0;
            end
        end
    end

    // Prescaled Clock
    // divider = 1,000,000 / (2 * f_note)  
    // generate 7 note frequencies
    localparam [15:0] C3_DIV = 16'd3822; // 130.81 Hz
    localparam [15:0] D3_DIV = 16'd3405; // 146.83 Hz
    localparam [15:0] E3_DIV = 16'd3034; // 164.81 Hz
    localparam [15:0] F3_DIV = 16'd2863; // 174.61 Hz
    localparam [15:0] G3_DIV = 16'd2551; // 196.00 Hz
    localparam [15:0] A3_DIV = 16'd2273; // 220.00 Hz
    localparam [15:0] B3_DIV = 16'd2025; // 246.94 Hz


    // Button handling
    // valid_chord = exactly one button is pressed
    wire [6:0] buttons; //7-bit signal line
    assign buttons = {BTN_C, BTN_D, BTN_E, BTN_F, BTN_G, BTN_A, BTN_B}; //assigns button names to each bit of the signal line (like an array) each to express a boolean value for pressed or unpressed 

    // define signal lines
    wire any_pressed;
    wire more_than_one;
    wire valid_chord;

    // button press checks
    assign any_pressed    = |buttons; //verilog 'reduction OR', "is any button pressed?"
    assign more_than_one  = |(buttons & (buttons - 7'd1)); //"is more than one button pressed"
    assign valid_chord    = any_pressed & ~more_than_one; //"is only one button pressed?"

    // Selected divider values for currently active chord
    reg [15:0] div1, div2, div3;

    // Chord Selection Logic Block
    // Chord case states for each button
    always @(*) begin
        // default = silence
        div1 = 16'd0;
        div2 = 16'd0;
        div3 = 16'd0;

        if (valid_chord) begin
            if (BTN_C) begin
                // C major = C E G
                div1 = C3_DIV;
                div2 = E3_DIV;
                div3 = G3_DIV;
            end
            else if (BTN_D) begin
                // D minor = D F A
                div1 = D3_DIV;
                div2 = F3_DIV;
                div3 = A3_DIV;
            end
            else if (BTN_E) begin
                // E minor = E G B
                div1 = E3_DIV;
                div2 = G3_DIV;
                div3 = B3_DIV;
            end
            else if (BTN_F) begin
                // F major = F A C
                div1 = F3_DIV;
                div2 = A3_DIV;
                div3 = C3_DIV;
            end
            else if (BTN_G) begin
                // G major = G B D
                div1 = G3_DIV;
                div2 = B3_DIV;
                div3 = D3_DIV;
            end
            else if (BTN_A) begin
                // A minor = A C E
                div1 = A3_DIV;
                div2 = C3_DIV;
                div3 = E3_DIV;
            end
            else begin
                // BTN_B
                // B diminished = B D F
                div1 = B3_DIV;
                div2 = D3_DIV;
                div3 = F3_DIV;
            end
        end
    end

    // Three independent tone generators
    reg [15:0] counter1, counter2, counter3;
    reg tone1, tone2, tone3;

    // Tone Generator Logic Block
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            counter1  <= 16'd0;
            counter2  <= 16'd0;
            counter3  <= 16'd0;
            tone1     <= 1'b0;
            tone2     <= 1'b0;
            tone3     <= 1'b0;
            audio_out <= 1'b0;
        end else begin
            if (valid_chord) begin
                if (audio_tick) begin
                    // tone 1
                    if (counter1 >= div1) begin
                        counter1 <= 16'd0;
                        tone1    <= ~tone1;
                    end else begin
                        counter1 <= counter1 + 16'd1;
                    end

                    // tone 2
                    if (counter2 >= div2) begin
                        counter2 <= 16'd0;
                        tone2    <= ~tone2;
                    end else begin
                        counter2 <= counter2 + 16'd1;
                    end

                    // tone 3
                    if (counter3 >= div3) begin
                        counter3 <= 16'd0;
                        tone3    <= ~tone3;
                    end else begin
                        counter3 <= counter3 + 16'd1;
                    end

                    // Chord Output
                    audio_out <= tone1 ^ tone2 ^ tone3;
                end
            end else begin
                // silence if zero or multiple buttons are pressed
                counter1  <= 16'd0;
                counter2  <= 16'd0;
                counter3  <= 16'd0;
                tone1     <= 1'b0;
                tone2     <= 1'b0;
                tone3     <= 1'b0;
                audio_out <= 1'b0;
            end
        end
    end

endmodule

Learnings from code above

  • Learned ‘wire’ signal line definition
  • Learned Verilog ‘reduction OR’ syntax
  • Learned ‘assign’ command

Running Project Code through the RTL to GDS Toolchain

  1. Launch Ubuntu 22.04 WSL

  2. Run Nano to write Verilog RTL code for the 7-Button Chord Synth project

    • Important: Locate the file in a specific location within the ‘src’ folder of the OpenRoad working directory
      alt text
      alt text
    • Save > CTRL + o, ENTER
    • Exit Nano > CTRL + x

  3. Write a sky130hd Configuration File in Nano

    • Important: Locate the file in ‘designs/sky130hd/7_button_chord_synth’
      alt text
      alt text
    • Save > CTRL + o, ENTER
    • Exit Nano > CTRL + x

  4. Synthesize Verilog RTL code into Logic Gates using Yosys

    Converting RTL code to Gate-level Netlist

    alt text

    ERROR!
    alt text
    - ChatGPT informed me that my module name generated a syntax error
    - The problem > my module name cannot start with a number
    - ‘7_button_chord_synth’ must be changed to ‘seven_button_chord_synth’

    The Fix

    • Edit the Verilog RTL file
      alt text

    • Edit the Config file as well (to reference the new module name)
      alt text

    Rerun Synthesis > Error - Again

    alt text
    - ChatGPT advised that this error has to do with the config file not defining a Timing Constant File
    - Timing Constant File (SDC) specifies: the clock, clock frequency, input/output timing

    The Fix
    - Create an SDC file (ChatGPT provided instructions) alt text
    - Edit the Configuration File > adding a new line to point to the SDC file
    alt text

    Rerun Synthesis > Success!!
    alt text

    read_liberty … sky130_fd_sc_hd_tt_025C_1v80.lib
    - loads sky130 Standard Cells

    read_lef sky130_fd_sc_hd.tlef
    read_lef sky130_fd_sc_hd_merged.lef
    - loads the technology LEF…describes metal layers, vias, cell dimensions

    link_design seven_button_chord_synth
    - confirms successful link to the Verilog design file - module name matched, syntax is correct, all signals resolved

    write_db ./results/…/1_synth.odb
    - an OpenRoad database of the circuit was created

    write_sdc ./results/…/1_synth.sdc
    - clock constraints successfully applied

    Elapsed time: 0:00.70
    - 0.7 seconds = fast compile hints at a small, Tiny Tapeout compatible circuit design


  1. Floorplanning

    • Floorplanning will…determine the chip area, place IO pins, create a power grid (ChatGPT)

    Run Floorplanning > ERROR!!
    alt text
    - ChatGPT tells me that the error has to do with the die area or core utilization specifications were not made
    - Must edit the Configuration File again > adding 2 lines for DIE_AREA (chip boundary) and CORE_AREA (boudary for Standard Cell placement)
    alt text

    Run Floorplanning > Success!!
    alt text

    ChatGPT provided the following insights about the Floorplanning results:

    Design area 4029 um^2 64% utilization
    - 4029 square microns of area required & 64% are used for logic cells (the remainder available for routing > good)
    - This compares to the Tone Generator design from Session 6 which was only ~500 square microns in size

    Inserted 77 tapcells
    - 77 Tapcells are used to connect substrate and wells to power, and prevent latch-up

    Inserting grid: grid
    - A Power Grid metal layers to distribute power was created (VDD, VSS)

    2_floorplan.odb
    - An .odb file was created (6_1_merged.gds) containing: chip boundary, power grid, tapcells, and empty placement rows
    - ..no standard cells were placed yet


  1. Logic Cell Placement

    The last step, Floorplanning, defined the boundary and power foundation for the logic gates, similar to the foundation and mechanical systems of a building I suppose. In this step, the actual Logic Cells (from sky130hd library) that will make up the 7 Button Monophonic Chord Synthesizer will be build on top.

    Run the Cell Placement > Success!!
    alt text
    alt text

    Wire Length Optimization (HPWL)
    - Original HPWL > 7056.1 - Final HPWL > 6372.1 - Delta > -9.8% - Insight: wiring reduced by ~10% - Implications: shorter wires, lower capacitance, faster circuit, lower power > good!

    Cell Mirroring
    - Mirrored 27 instances - Implications: better pin line-up, shorter wires > good!

    Placement Violations
    - zero > placement is “physically legal”
    - Implications: no overlapping cells, rows are valid, power rails line up

    Updated Area
    - Design Area > 4543 square microns (up from floorplanning 4029 square microns)
    - Utilization > 72% (up from floorplanning 64% utilization) > still good! (50-70% is ideal)

    Files Generated
    - Another .obd file (3_place.odb) generated containing: exact cell location, estimated parasitic wiring


  1. Clock Tree

    Time to distribute the clock into the hardware layout, adding clock buffers, balance clock arrival times, prevent clock skew. The clock was previously represented as a single logical signal, which must reach all the Flip-flops on the chip. The CTS process builds a balanced clock distribution network using buffers…keeping clock arrival times nearly equal across the chip (ChatGPT)

    alt text

    Run Clock Tree > Success!!
    alt text
    alt text

    Repair timing output passed lec test
    - buffers inserted
    - timing optimized
    - logic behavior verified > Verilog design still functions as expected
    - LEC = Logic Equivalence Check completed

    Implications: - Original HPWL > 7307
    - Legalized HPWL > 7577
    - Delta > 4%
    - …slight HPWL increase due to clock buffer and wire addition

    Updated Chip Size:
    - Design Area > 4848 square micron (up from 4543 square microns)
    - Utilization > 77% (up from 72% square microns) > not idea, still safe (within 70-80% range, at 85% becomes problematic)

    Displacement Metrics
    - Total Displacement > 204.5 microns
    - Average > 0.3 microns
    - Max > 13.6 microns
    - …some cells were moved to make room for the Clock Tree
    - …movement very small, placement still good

    File Generated
    - Another .odb file (4_cts.odb) created that contains: placed Standard Cells, Clock Buffers inserted, Preliminary Clock Routing
    - Signal Wires are still NOT routed yet

  2. Routing

    In this step, metal wires, vias, power connections and a clock network will be created. Reports will be generated.

    Run Routing > Success!!
    alt text
    alt text

    Elapsed time: 1:13.22
    - (this process took a while…)
    - According to ChatGPT, this step assigned metal layers, avoid wire crossings, placed vias, and obeyed design rules…computing “thousands of possible wire paths” to pick the “legal ones”
    - Analogous to routing a PCB in KiCAD, I guess. Like trying to solve a puzzle.

    Placed 352 filler instances
    - 352 Empty Standard Cells added between logic cells
    - …to “maintain well continuity, power rails and satisfy fabrication rules”
    - Well Continuity is a physical-silicon concept…the doped region between the Metal Layer (top) and the Silicate Substrate (bottom) where CMOS and NMOS transistors “live”
    - Wells must be continuous…to avoid voltage float, leakage, latch-up, incorrect transistor thresholds and chip failure
    - …Empty Cells are used to fill gaps and maintain continuity

    Files Generated:
    - Two new .odb files were generated (5_route.odb and 5_3_fillcell.odb)
    - …the chip layout is now fully connected!!

  3. Final Analysis & GDS Export

    According to ChatGPT, in this Final Stage many verification analysis and checks are performed including:

    • Final Timing analysis
    • IR Drop Analysis
    • Antenna Checks
    • Final Design Rule Checks (DRC)

    …and ends with a GDS file (actual silicon mask layout) export.

    Feels like the Boss Battle in a game.

    Run Final State > Success!!
    alt text
    alt text
    alt text

    cp results/sky130hd/seven_button_chord_synth/base/6_1_merged.gds
    - Confirms the export of the all important GDS file for Fabrication - Contains: Transistor geometries, wells, diffusion, metal wires, vias, power rails

The 7 Button Diatonic Chord Monophonic Synthesizer ASIC > Report

  • ~450 logic cells
  • ~350 filler cells
  • ~4800 square microns in size
  • 77% Utilization

alt text

Synthesis Report alt text

Summary: Standard Cell Counts
- NOR2 > 71
- D Flip-Flops > 59
- each with ~24 transistors
- so ~1400 transistors for Tone Counters, State Registers & Button Logic State
- NAND2 > 42
- Half Adders > 41
- for Note Frequency Dividers
- ~570 transistors
- Combinational Logic > ~250
- Inverters, XNOR, XOR gates
- for Button Selection, Chord Mapping, Comparator Logic, Tone Toggling
- ~2000-2500 transistors - Total > 465 Standard Cells (including Combinational Logic), ~4000-~4500 transistors

Finished Report

ChatGPT reviewed my final report and identified a problem:

  • The design does not meet the clock target asked for

  • Required time to achieve fmax > 2.197

  • Finish Critical Path Delay > 2.3574
  • Finish Critical Path Slack > -0.2277

Recommendation:
- Reduce fmax to 400MHz - Change the export CLOCK_Period configuration from 1.9 to 2.5
- “The desired audio frequencies are extremly low compared to hundreds of MHz, so the chip doesn’t need to run ultra fast”

Report Conclusions:
- Routing & Layout > success
- Fabrication File > success
- Timing > slightly failing
- Clock target > too aggressive

  • Adjust the export clock in the config.mk file and constraint.sdc to 2.5
  • run make clean command
  • rerun the process

Results After Adjustment and Rerun alt text
alt text

timing now met, timing closure achieved - Finish Critical Path Delay > 2.6057
- Finish Critical Path Slack > 0.1244

Power Consumption
- Sequential > 0.954 mW
- Clock > 1.09 mW
- Combinational > 0.13 mW

DRC & LVS

DRC ran without issue. Here is the DRC Report.

LVS was harder. With the toolchain I had, mostly a collection made available by installing OpenRoad, the .tech file needed for LVS check with Magic was missing. After debugging with ‘Chatty’ for a while, the conclusion was to go with KLayout for the LVS check.

Error in Magic
alt text

.tech File not Found
alt text

Support File Mismatch
alt text

A “Failing Subcircuit

.SUBCKT sky130_fd_sc_hd__macro_sparecell1 VGND VNB VPB VPWR LO

This definition has 5 pins. But later inside CDL (circuit description language, i.e. SPICE), the same cell is being instantiated with 6 or 7 pins…causing LVS Parser to stop. ChatGPT describes this as a “quirk of the OpenRoad-flow-scripts Sky130 CDL” when used with KLayout LVS.

Some “Spare Cells Definitions” don’t parse cleanly, causing “pin count mismatch between circuit definition and circuit call”

Fortunately, the problematic cell is a “Filler Cell” and inconsequential for the functionality of the project circuit. So it can be safely removed for LVS testing.

alt text

One error managed…another one appears.

alt text
- A Passive Device Line is encountered that does not have a required value.
alt text
- Two resistors do not have values

alt text
- Arbitrary values added to satisfy LVS requirements
- LVS does not concern itself with values, only connectivity

But another error appears…
alt text

At this point, ChatGPT advises that the issue is not with the chip design, but with syntax incompatibility between OpenRoad CDL, KLayout LVS and Sky130 device. To stop chasing ghosts, ChatGPT states the solution will require the installation of the full Sky130 PDK stack (a multi-gigabyte install)…then run LVS with Magic and Netgen.

I am choosing not to do this. As ChatGPT states that my design is a pure standard-cell digital logic so LVS checks should not be a problem.

WEBP Images

Generated as part of the final report by OpenRoad. The WEBP files can be opened in Windows from WSL by…

  1. Navigating to the correct folder
cd ~/OpenROAD-flow-scripts/flow/reports/sky130hd/seven_button_chord_synth/base  
  1. Run the Explorer command
explorer.exe .  

Final Placement
Final Placement

Final Routing
alt text

Final Clock
alt text

Final Worst Path
alt text

The image shows the Critical Timing Path traced across the chip layout…specifically the longest delay path in the entire chip

  • Green rectangles indicate the destination flip-flop
  • Pink indicates the signal gates along the path…each gate adds propagation delay
  • Green Lines indicate the connecting pathways
  • This is the Worst Path because it involve several Logic Gates, has long wire routing
  • In the project…this path likely represents the path moving from…tone counter register > comparison logic > button/chord selection logic > audio_out register
  • This path is the chip deciding when to toggle the square wave output
  • Fmax = 1 / critical_path delay

alt text - The Clock Period was set at 2.5ns - Worst Slack Max: 0.12ns
- Slack Adjusted Timing 2.5ns - 0.12ns = 2.38ns - Fmax: 1/2.38ns = 420MHz

Final Congestion
alt text

Final IR Drop
alt text

Viewing the Chip in KLayout

The 7 Button Diatonic Chord Monophonic Synthesizer ASIC

alt text

Now let’s break down this image…

(wip)

Learning with ChatGPT

I used ChatGPT extensively during this course to accelerate my learning. There was so much (difficult new topics) to learn in such a short period of time, and ChatGPT served as my personal tutor and advisor. To avoid “Chatty” simply doing my homework for me, I asked many supplementary questions and read all of the explanations throughout the process. And I learned a ton.

Reflection

Wow. I can hardly believe I actually got this far.

Definitely could not have succeeded without ChatGPT’s guidance and assistance, but I learned a ton going through the process methodically from beginning to end. I probably missed a few things required for class…but this is what I am able to manage for now.

Final Project > Deliverables