Introduction
Today, we're going to get started with the basics of using Wokwi for integrated circuit design. Wokwi can be used to simulate a variety of tech, but we're going to be looking at a digital circuit design — specifically, a countdown timer using Verilog. Verilog is an HDL (Hardware Description Language) used to model circuits, including ASICs and FPGAs. A timer is a great beginner project because it lets us include a lot of basic Verilog functions in one design.
Setting Up Wokwi
To start, make an account in Wokwi and open a blank file. Make sure to select Verilog as the language, not C. You'll see two tabs right away: a readme file (which you can edit to describe your project) and a diagram.json file.
A JSON file is a file format for data structures — it holds data in a human-readable way. In our diagram.json file, we can see three sections:
- Parts — the physical components used in our circuit
- Connections — the wires connecting those parts
- Libraries — third-party libraries (not needed for this design, but useful for more complex ones)
Adding Parts
Click the blue circle with a plus sign to add parts. For our timer, we need:
- A seven segment display
- A button (to increase the count)
- A switch (to start the countdown)
- VCC and GND (for the button and switch)
- A clock
- A custom chip — name it timer
As soon as you add the custom chip, two new files appear: a chip.json file (describing the chip's pinout) and a chip.v file (the Verilog file where we tell the chip what to do).
Defining the Chip Pins
In chip.json, inside the square brackets, add the names of the pins we need: clock, button, switch, and pins a through g for the seven segment display.
Connecting the Circuit
Now connect everything up:
- Clock → clock pin
- Bottom left of button → button pin
- Middle pin of switch → switch pin
- Display pins a–g → their respective chip pins
- Common pin of display → external GND
- Switch and button → GND and VCC (make sure GND and button pins are across from each other)
By wiring the button this way, we make it active high — meaning the signal is 1 only when the button is pressed. Active low is the opposite, where pressing gives a 0.
Writing the Verilog
Verilog code is organized into modules — blocks of code for specific uses. In the module, we declare whether each pin is an input or output, and whether it's a wire or a register.
- Wires — continuously transmit data without storing values. Input pins are always wires.
- Registers (reg) — store data. Output pins can be wires or registers.
Button Logic
We need a register to track button presses and count up to 9:
always @(posedge clock) means this block runs on every clock tick. With a 100 kHz clock, that's 100,000 times per second. We use button && !button_off to detect a fresh press, not a held button.
Seven Segment Display Function
We use a function to convert a count value (0–9) into the correct 7-bit pattern for the display:
Countdown Logic
We count clock ticks to track seconds. At 100 kHz, one minute = 6,000,000 ticks. We need 23 bits to store that (2²³ = 8,388,608).
When the switch is on, we count ticks and decrement the display every minute. When the switch is off, we show the current count (set by the button) and reset the seconds counter. The single line {a,b,c,d,e,f,g} = seg7(count) does all the display work — it assigns each bit of the function's output to the corresponding segment pin.
What is a Multiplexer?
A multiplexer, or mux for short, is a common combinational circuit. Essentially, it receives multiple inputs and outputs a single signal value. A 2:1 mux will receive 2 signals, select 1, and output the selected signal. They can be written using cases, bit values, else-if statements, and assignment.
Muxes can be used for a variety of circuits and are the cleanest and most efficient way to select a single signal from a variety of input signals.
Muxes and Multi-Digit Displays
One of the best ways to begin with muxes is through a multiple digit display with shared segment pins. With shared segment pins, it's impossible to have multiple digits light up at the same time. Instead, each digit is lit up while the other remains off, and they constantly swap at incredibly fast speeds — the switching is virtually invisible to the human eye after speeds of 50–60 Hz, which is what old TVs used to achieve a smooth display.
The toggle speed increases for LED displays like clocks and timers to around 100–200 Hz, while going past 2 kHz switches too fast for the circuit and will result in a dimly lit display. A good target is anywhere from 100 Hz to 1 kHz, and it depends on the system clock of the circuit.
Building a Sequential Mux
A great way to start is with sequential muxes based on clock edges. First, create a register called mux and set it equal to 0. We want it to switch from 0 to 1 at a frequency within the toggle range, so we need to "slow down" the system clock.
To do so, create a new register called muxcount with 9 bits and set it equal to 0, then have it increment with every clock tick until it resets. 9 bits, 8 bits, and 7 bits all work well — 10+ bits would make the frequency too slow, since more ticks means more time between resets.
Under a posedge block, muxcount increments 2⁹ (512) times before it resets to 0 — given the [8:0] restriction — by setting muxcount <= muxcount + 1. Now, muxcount increases with every tick and resets to 0 after 512 ticks.
How the Mux Works
To do so, we want mux to switch its value from 0 to 1 when muxcount resets after every 512 ticks. So, we can write if(muxcount == 0), then mux <= ~mux. This changes mux from itself to not mux, or changes it from 0 to 1, everytime muxcount resets to 0.
Sequential Logic
Verilog runs primarily on two different types of logic — sequential and combinational. Sequential logic runs based on current and past input, and depends on clock edges. Sequential logic will be accompanied by an always @(posedge clock) or an always @(negedge clock), although using a positive edge is much more common.
Combinational Logic
Combinational logic runs without storing input, therefore constantly updating values to take in immediate input changes. Instead of running based on clock, it's triggered with input changes. always @(*) and always_comb are used to signify a combinational logic block.
To Wrap Up
Sequential logic is used when storing and comparing values, while combinational logic is used when converting, routing, or updating values immediately.
Non-Blocking vs Blocking Assignment
Sequential and combinational logic each have their own assignment operators. Sequential logic uses non-blocking assignment, represented by <=, while combinational logic uses blocking assignment, represented by =. Thus, <= will be found under posedge clock sections and = will be found under always @(*) or always_comb sections.
A good rule of thumb to remember is to use <= when comparing a value to its previous state and updating a value based on previous states. An = should be used for value updates not depending on previous states. A classic example of the difference is below:
<= because its output depends on what it held last clock cycle. A combinational adder uses = because it just computes immediately from its current inputs.The assign Keyword
The third type of assignment is the keyword assign itself, which is combinational but can only be used outside of always blocks and can only use =, not <=. The assign keyword is also good for one-line assignments, simple muxes, logic gates, math expressions, and signal assignment.
Bounce vs No Bounce
These tutorials use no bounce for beginner simulation. In real life, buttons "bounce" when pressed — when an actual button is pressed, the metal inside doesn't just touch each other a single time but often comes into contact many times in microseconds, sending an influx of unnecessary and confusing signals through the circuit. To counter this, we use something called a debouncer, which is code that calculates how long the button is pressed for and sends a signal to reduce bounce.
Adding the Debouncer
First, add a new register called button_stable in addition to button and button_off from the countdown tutorial. Also add a 10-bit register called debounce_cnt. Set both equal to 0.
Under our posedge clock block, write button_off <= button_stable. This says that when a clock edge is detected, button_off is set equal to the past value of button_stable. Next, we want to count how many clock ticks pass following the press so we can create a stable signal. We do this with if statements — if button == 1 and debounce_cnt reaches 500, assign button_stable to 1. Otherwise, while the button is still held but the count hasn't reached 500 yet, increment debounce_cnt. When the button is not pressed, reset both debounce_cnt and button_stable to 0.
Why button_off <= button_stable?
The whole point of this line is so we can compare button_stable and button_off to each other. Because button_off <= button_stable sets button_off to the previous value of button_stable, we can compare the current state of the button to its previous value — letting us detect exactly when the button state changes. When button_off and button_stable are different, we know a change just happened. The condition if (button_stable && !button_off) is true when the button is currently pressed but wasn't pressed last tick — a fresh, bounce-free press.
Common Fixes
Using <= vs =
Remember that <= belongs in posedge clock blocks and = belongs in always @(*) combinational blocks.
Cathode vs Anode
Make sure to match parts with whatever hardware you are planning on using. You can check hardware common pins on their data sheets. These tutorials use a common cathode. You can hover over parts in Wokwi to change the common pin. For a cathode, connect the common pin to GND. For an anode, connect it to VCC. If using a common anode display, put a ~ in front of the output assignment so it becomes:
begin and end
Make sure to use begin and end after if statements when multiple lines are dependent on the same if statement.
Semicolons and Closing Keywords
Remember semicolons after lines of code, and close out functions, modules, cases, and blocks with endfunction, endmodule, endcase, and end.
Register Bit Width
When using a register with multiple bits, make sure the maximum value you want to store is less than 2 to the power of the number of bits.
What is a Clock?
In IC design, "clock" doesn't mean a watch or a wall clock with hour and minute hands. However, system clocks, as they are called, and regular clocks have a similar function — they both keep time. System clocks within a circuit do this by sending timed and repetitive signals, similar to a metronome within an orchestra, or a very accurate and incredibly fast heartbeat. Clock speeds are determined by Hz, which is in terms of cycles, or "beats" per second.
How Do Clocks Send Signals?
Most clocks are made from quartz crystals that vibrate when in contact with an electrical signal. These vibrations are then translated into clock "ticks", that can be used to "count" time within a circuit to make sure everything runs in time and functions correctly. Different crystals have different vibration speeds with different signals, allowing us to precisely change the Hz, or frequency/speed of these clocks.
What Are These Crystals?
The crystals used in clock oscillators are made of quartz, a naturally occurring mineral composed of silicon dioxide (SiO₂). While quartz is found in nature, the crystals used in electronics are almost always lab-grown to ensure purity and consistency. They are cut into thin wafers at very precise angles — the angle of the cut actually determines the resonant frequency of the crystal.
How Do They Create Vibrations?
Quartz crystals exhibit something called the piezoelectric effect — when mechanical pressure is applied to them, they generate an electric charge, and conversely, when an electric voltage is applied to them, they physically deform. When an alternating electrical signal is applied, the crystal rapidly flexes back and forth, creating mechanical vibrations. These vibrations are extremely stable and consistent.
How Long Do the Vibrations Last?
On their own, the vibrations would slowly die out — but the oscillator circuit continuously feeds energy back in to sustain them, so in practice the crystal vibrates indefinitely as long as power is supplied. The precision of quartz is remarkable — a typical quartz crystal oscillator drifts by only a few parts per million per year.
Are All Clocks Crystal?
No — there are several types of clock sources used in IC design, each with different trade-offs.
RC Oscillators
Built entirely on-chip using a resistor and capacitor. Much less precise than crystals — frequency drifts with temperature and voltage — but cheap and convenient since no external parts are needed. Common in low-cost microcontrollers and situations where exact timing doesn't matter much.
Ring Oscillators
Made from an odd number of inverters connected in a loop. Very simple and fully on-chip, but not very stable. Mostly used for testing, characterizing a chip's process speed, or as a building block inside PLLs.
PLLs (Phase-Locked Loops)
Extremely common in modern ICs. A PLL takes a reference clock (often from a crystal) and can multiply, divide, or shift its frequency on-chip. This lets a chip run its internal logic at 1 GHz while only needing a cheap 25 MHz crystal externally. Almost every modern processor and FPGA uses PLLs.
MEMS Oscillators
A newer alternative to crystals that uses microscopic mechanical structures instead of quartz. Fully integratable, more resistant to vibration, and increasingly replacing crystals in modern designs.
Overview
There are a lot of data types in Verilog, used to describe the different connections and responsibilities.
Wires
Wires are used when we want to have a continual stream of information. They're constantly transmitting data without storing any values. Pins are implicitly defined as wires, so input x is the same as input wire x. Input pins will always be wires since they are continuously sending data.
Registers
Registers are used when we want to store values for future comparison. Output pins can either be wires or registers depending on their specific application within a circuit. Registers can either hold individual values, or arrays.
Parameters
Parameters are constants whose value is determined and set during compilation. They can later be overridden if necessary.
Overview
Verilog has a wide range of operators for performing logic, arithmetic, comparisons, and bit manipulation. They fall into several categories depending on what they do.
Arithmetic Operators
Used for basic math on values. + for addition, - for subtraction, * for multiplication, / for division, and % for modulus (remainder after division).
Logical Operators
Used in if statements and conditions. They return a single 1-bit true or false result. && is logical AND (true if both sides are non-zero), || is logical OR (true if either side is non-zero), and ! is logical NOT (flips true to false and vice versa).
Bitwise Operators
Operate on each bit individually across a vector. & is bitwise AND, | is bitwise OR, ~ is bitwise NOT (inverts all bits), ^ is bitwise XOR, and ~^ or ^~ is bitwise XNOR.
Reduction Operators
Apply a bitwise operation across all bits of a single vector and return one bit. & ANDs all bits together, | ORs all bits together, and ^ XORs all bits together — useful for parity checks. ~& is NAND all bits and ~| is NOR all bits.
Relational Operators
Used for comparisons — they return 1 (true) or 0 (false). == is equal to, != is not equal to, < is less than, > is greater than, <= is less than or equal to, and >= is greater than or equal to.
Shift Operators
<< is logical shift left (fills with 0s), >> is logical shift right (fills with 0s), <<< is arithmetic shift left, and >>> is arithmetic shift right (preserves the sign bit).
Concatenation and Replication
{} is concatenation — it joins two or more vectors together. For example, {a, b} joins signal a and signal b side by side. {{}} is replication — it repeats a value. For example, {4{1'b0}} gives you four zeros: 4'b0000.
Conditional Operator
? : is the ternary operator — it works like a one-line if-else. For example, assign y = (a > b) ? a : b; means if a is greater than b, y gets a, otherwise y gets b. Very commonly used with the assign keyword.
+, -, ==, !=, >=, <=, &&, !, ~, ^, {}, and ? :. The others are useful to know but less common in everyday designs.This tutorial is going to assume you already have the parts and pinout required for the single digit tutorial. To change this to two digits, we have to add a multiplexer and alter a few variables. Please look over the multiplexer article and the one-digit timer tutorial for a refresh if necessary.
In addition to the pinout from before, the two-digit timer has a slightly different pinout, since each digit has its own pin. In the chip.json file, we'll add two more pins — dig1 and dig2. Then in our verilog file, we'll add two more reg outputs — reg output dig1 and reg output dig2.
In the one-digit timer tutorial, we only had one input-defined variable that we needed to change — count. For a two digit timer, we're going to need two separate variables for the ones and tens digit, which we can name ones and tens. They're both going to hold [3:0] values just like count.
This is what we want to happen: when the button is pressed, we first want to check if ones is 9. If so, then we want to reset ones to 1. If ones is 9, we also want to check if tens is 9. If tens is also 9, then we want to reset it to 0 also. If ones is 9 but tens is not 9, then we want tens to increase by 1. If ones is not 9, then we want ones to increase by 1. This sounds convoluted but is quite simple to express using looped if statements, as shown below. Remember to use non-blocking assignment, as we need to compare past values.
Next, we'll get our mux working. For this specific situation, we want a register mux that's equal to 0, and a counter for mux that determines when it changes values, which we can call muxswitch. We want muxswitch to hold around 500 values to sufficiently "slow down" our clock, so we can make mux a reg that holds [8:0] values. By doing so, the value of muxswitch will reach 512 and then automatically set itself to 0. To actually change the value of mux from 0 to 1, we can use another if statement. First we want muxswitch to increase by 1 with each clock tick, and since we're already in a posedge block we set muxswitch equal to itself plus 1. Since muxswitch will automatically reset to 0, we can use an if statement that says when it does reset to 0, then change the value of mux from 0 to 1 or 1 to 0, using a not operator (~) so it becomes the opposite of its current state.
To decrease the first and second digit display for the timer, we can actually do the opposite of what we did when we wanted to increase them based on the button. When the switch is on, we want seconds to increase by 1 every tick and then reach 6000000 ticks (1 minute) like we needed for the one-digit timer, and then reset it to 0. Similar to before, this is what we want to happen: If ones is 0, and tens is greater than 0, then we want tens to decrease by 1 and ones to reset to 9. If ones is not 0, then we want ones to decrease by 1. Lastly, when seconds is not 6000000 we want it to increase by 1 so we'll set it equal to itself plus 1. Our function for the display values stays the same from before.
The seg7 function stays the same as the one-digit timer, but now takes a digit input, the same size [3:0], so it can be called for both ones and tens.
Now, let's put it all together — our mux, button, and display blocks! First, we want to start with a combinational block — always(*). This is because in this code, we're not comparing any values or relying on clock ticks. We want the display to continuously and quickly update without comparison (our other code blocks take care of that) based on input changes, so combinational logic is exactly what we want. The point of our mux switching values is so we can switch the digits on display — the ones and the tens digit. We can make ones on when mux is 0, and tens on when mux is 1. So when mux is equal to 0, then we can set the pins {a,b,c,d,e,f,g} equal to sevseg(ones). We also have to set the pin dig1 equal to 1 and the pin dig2 equal to 0, so only one digit will be on. Else, when mux is equal to 1, we'll turn on dig2. Now, {a,b,c,d,e,f,g} will be equal to sevseg(tens) and dig1 equals 0 while dig2 equals 1. Now, close out the module, and congratulations on your two-digit timer!