Intuition The ONE core idea
An HDL is a way to write down a picture of a circuit using text — so when you type assign y = a & b; (a line we fully unpack in §5b) you are really drawing an AND gate with two input wires and one output wire. Everything else in this topic is just learning which words draw which wires, and — crucially — remembering that all those drawings exist at the same time , not one after another like a recipe.
Before you can read a single line of the parent note, you need to recognise the raw pieces it throws at you: what a wire is, what a bit is, what &, [7:0], 8'hA5, posedge, <=, assign, always, module each mean and look like — in both Verilog and VHDL. This page builds every one of them from nothing.
A bit is a single physical wire that is either high (voltage present, we call it 1) or low (no voltage, we call it 0). That is the core alphabet of digital hardware — but real wires can also sit in two extra states that matter in simulation (defined in §2b below).
Intuition Why only two (mostly)?
A wire is easy to build if we only ask "is there voltage or not?" — a fuzzy "kind-of-half voltage" is unreliable and gets destroyed by noise. So hardware bets everything on two clean states. Every number, letter, and instruction in a computer is just many of these wires side by side.
Reading the figure: four horizontal lines, each one is a single wire drawn on the board. The top pale-yellow line is held high (1); the blue line below it is low (0). The pink line is x — the simulator drew it as a "muddy" line because it does not know the value. The dashed off-white line is z — dashed because nobody is driving it , so it is floating and electrically invisible. The little arrow reminds you that on real silicon only the top two ever occur; x/z are simulation bookkeeping. Keep this picture — every symbol below is built out of these lines.
See Number Systems and Bit-Widths for how many bits make numbers.
A bus is several bit-wires travelling together as one named group. The notation [7:0] means "8 wires, numbered from 7 down to 0."
Reading wire [7:0] bus; from the parent note out loud:
wire — "this is a bundle of plain wires (no memory)."
[7:0] — "the index of the top wire is 7, the bottom is 0" — so there are 7 − 0 + 1 = 8 wires.
bus — the name we gave the bundle.
MSB (Most Significant Bit) = bit 7 here: it carries the largest place value.
LSB (Least Significant Bit) = bit 0: the smallest place value.
"Significant" = "how much this bit is worth when we read the whole bundle as a number."
Reading the figure: eight boxes stand in a row — each box is one wire of the bus. Above each box is its index (7 on the far left down to 0 on the far right); below it is its place value 2 index . The wires carrying 1 are drawn yellow, the 0 wires blue, so the pattern 10100101 is visible at a glance. The pink arrows label the leftmost box as the MSB (worth 128) and the rightmost as the LSB (worth 1). Add up only the yellow boxes — 128 + 32 + 4 + 1 — and you get 165, which the caption confirms equals 8'hA5. This single picture ties together the bus, MSB/LSB, place values, and the hex literal.
[7:0] and not just "8 wires"?
Because hardware wires are physical and fixed in number — you cannot magically grow a 9th wire. The parent note stresses this: "hardware wires have a fixed number of physical lines." The [7:0] is you promising the tool exactly how many lines to lay down.
Intuition Why slicing matters
A 32-bit instruction might pack an opcode in instr[31:26] and a register number in instr[25:21]. Slicing is how you say "route just these physical wires onward" — literally tapping a subset of the ribbon.
Reading the figure: the same 8-wire ribbon from before. A pink bracket wraps boxes 7–4 and is labelled bus[7:4] — a part-select that pulls those four wires off as a smaller 4-wire ribbon (drawn peeling away to the right). A separate blue arrow points at the single box 3, labelled bus[3] — a bit-select that taps exactly one wire. Notice the untouched wires just continue straight: slicing does not copy or compute, it simply routes a chosen subset of physical lines onward .
Intuition Why does hex show up so much in HDL?
One hex digit packs exactly 4 bits (because 2 4 = 16 ). So 8'hA5 splits neatly into two hex digits: A = 1010, 5 = 0101, giving 10100101. Writing A5 is far shorter and less error-prone than eight 0/1s. That is the whole reason the parent note uses 8'hA5.
8'hA5
A = 10 = 101 0 2 , 5 = 010 1 2 . Concatenate: 10100101.
As a number: 128 + 32 + 4 + 1 = 165 . (Bits 7, 5, 2, 0 are high.)
x and high-impedance z
A simulated wire has four possible states, not two:
0, 1 — the clean values you know.
==x== — unknown : the simulator does not know if it is 0 or 1 (e.g. two drivers fighting, or an uninitialised register).
==z== — high-impedance : the wire is floating , driven by nobody — like a disconnected pin.
Inside a sized literal you can mix them: 4'b1xz0 = bit3 1, bit2 unknown, bit1 floating, bit0 0.
Intuition Why simulation needs
x and z
Real silicon only ever has 0/1, but before you power it up a flip-flop holds "who knows what." x lets the simulator honestly say "undefined — fix your reset." z is the language for tri-state (see §11): a driver that lets go of the wire so someone else can drive it.
The Verilog pattern WIDTH'BASE VALUE means "a constant, this many wires wide, written in this base."
8 — width: eight wires.
' — a separator (an apostrophe), just punctuation.
h — base is hexadecimal.
A5 — the value.
So 4'b1010 = "4 wires, binary, value 1010" and 8'hA5 = "8 wires, hex, value A5". Every literal states its width because — again — a constant has to fit onto a fixed number of physical lines.
Definition signed / unsigned
By default a Verilog vector is unsigned — every pattern is a plain non-negative count, so 4'b1000 = 8.
Declaring reg signed [3:0] x; (or wrapping $signed(...)/$unsigned(...)) says "read the top bit as a sign ": then 4'b1000 = − 8 using two's complement.
Intuition Why sign matters — and sign extension
When you widen a signed number (say 4 bits into 8), you must copy the sign bit into the new top bits so the value stays the same: 1101 (−3) becomes 11111101 (still −3). This copying is sign extension . Forget it and a subtraction silently gives a huge positive number. The $signed() / $unsigned() functions are how you tell the tool which rule to use. See Number Systems and Bit-Widths .
The braces =={high, low}== glue bundles together, left = high bits, right = low bits .
{4'b1010, 4'b0101} places 1010 in the top 4 wires and 0101 in the bottom 4, producing the 8-bit 10100101 — the same 165 as 8'hA5. The picture: two 4-wire ribbons taped end to end into one 8-wire ribbon.
Definition Bitwise operators (act wire-by-wire)
==&== = AND : 1 only if both inputs are 1.
==|== = OR : 1 if at least one input is 1.
==~== = NOT : flips 0↔1.
==^== = XOR (exclusive-or): 1 if the inputs differ .
==~^ or ^~== = XNOR : 1 if the inputs are the same (XOR then NOT — the two spellings mean the same gate).
Definition Logical operators (act on the whole value → one bit)
==&&== = logical AND, ==||== = logical OR, ==!== = logical NOT.
These treat a whole bus as one true/false: non-zero → true, zero → false, and give a single 1/0. Used in if (a && b), not for wiring gates.
& vs &&
a & b on 4-bit buses ANDs each of the 4 wire-pairs → a 4-bit result. a && b asks "is a non-zero AND b non-zero?" → a single bit. Using the wrong one builds the wrong hardware.
are gates
When you write y = a & b, the tool does not "compute" at runtime — it lays down a physical AND gate with a and b as inputs and y as output. XOR is the heart of adders and parity checkers; the symbol is a drawing instruction.
Reading the figure: three standard gate outlines drawn in chalk. The flat-backed bullet is AND (&), the curved-back shield is OR (|), and the double-curved shield with the extra tail is XOR (^). Each has two wires coming in from the left (the fan-in : the inputs a and b) and one wire leaving to the right (the fan-out : the output y). The truth beneath each gate reminds you when the output goes high. This is what a bitwise operator becomes — a soldered shape, not a calculation.
assign (continuous assignment)
==assign== is the Verilog keyword that creates a continuous (always-live) connection. The full form is
assign y = a & b;
Read it as: "let the wire y forever equal the AND of a and b." There is no "run once" — the moment a or b changes, y re-evaluates by itself.
assign — the keyword announcing a continuous connection.
y — the left-hand side: the wire being driven (must be a wire, never a reg).
= — here it means "is permanently equal to."
a & b — the right-hand side: the logic that feeds y.
assign is the "instant" connection
assign is how you draw the combinational gates of §5 straight onto the board. It has no memory and no clock — it is a pure wire-through-a-gate. Two separate assign lines are two separate gates existing simultaneously ; their order on the page is irrelevant. This is the combinational half of HDL, and the direct opposite of the clocked always block in §6b.
A clock is one special wire that flips 0 → 1 → 0 → 1 … forever at a steady beat. It is the "heartbeat" that tells memory elements when to update.
Definition posedge and negedge (the two edges)
==posedge clk== = the instant the wire goes from 0 up to 1 (rising edge).
==negedge clk== = the instant it goes from 1 down to 0 (falling edge).
pos /neg = positive-/negative-going; edge = the moment of change, not the whole high or low time.
Reading the figure: the top trace is the clock wire drawn as a square wave — flat low, jump up, flat high, jump down, repeat. Each rising jump is circled and labelled posedge (in blue); each falling jump is circled and labelled negedge (in pink). The flat stretches between edges carry no update — only the circled instants do. That is the whole idea of an edge: not the level, but the moment of change .
always — the procedural block
==always== is a Verilog keyword that starts a procedural block : a chunk of statements that re-executes every time one of the events it is "sensitive" to occurs. It is the opposite of assign: instead of one permanent equation, it is a "whenever this happens, do these steps" recipe.
always @(EVENTS) begin
... statements ...
end
always — "keep re-running this block, forever, on the listed events."
@(EVENTS) — the sensitivity list : which events wake the block (§9).
begin ... end — brackets that group the statements inside (like { } in C).
With @(*) it builds combinational logic; with @(posedge clk) it builds clocked memory. (There is also initial, a block that runs once at time zero — used only in Testbenches , never in synthesizable hardware.)
if and else (conditional keywords)
Inside a procedural block, ==if (condition) runs its statement only when the condition is true, and else== gives the fallback when it is false.
if (rst) — "when the reset wire is high…"
else — "…otherwise…"
In hardware these do not mean "branch and jump" like software — they describe a multiplexer : a selector that routes one of several inputs to the output depending on the condition wire.
Definition Multi-event sensitivity:
@(posedge clk or negedge rst)
You can list several edges joined by ==or==. The classic pattern
always @( posedge clk or negedge rst) begin
if ( ! rst) q <= 1'b0 ; // rst dropping to 0 clears immediately
else q <= d; // otherwise update on the clock edge
end
This is an asynchronous reset : the block wakes on either a clock rise or the reset falling, so the register can be cleared without waiting for the clock. Here if (!rst) uses the logical-NOT ! from §5 — "if reset is not high (i.e. it just went low)."
Intuition Why do we need "the edge" at all?
A plain wire (assign) reacts continuously — it has no sense of time, so it cannot remember . To store a value you need a moment that says "now, capture and hold." That moment is an edge. This is the border between Combinational vs Sequential Logic — no clock means combinational, a clock edge means sequential. See Flip-Flops and Latches for the box that remembers.
Definition wire vs reg (Verilog)
==wire== — a plain connection, driven continuously by assign. No memory.
==reg== — a name that can hold a value between events; required for anything assigned inside an always block.
Warning on the name: reg does not always mean a hardware register. It only means "this signal is assigned procedurally." Whether it becomes real memory depends on whether it is inside a clocked block.
= vs Non-blocking <=
===== (blocking) — "do this now , before moving on." Models a plain wire; used in combinational always @(*).
==<=== (non-blocking) — "everybody read your input first, then all update together ." Models flip-flops sharing one clock; used in always @(posedge clk).
You do not need to master these yet — the parent note dedicates its whole core section to them. Here just anchor the symbols : <= looks like the up-arrow of an edge, = is the plain instant-equals. The distinction matters because Synthesis vs Simulation can disagree if you pick wrong.
Definition Conditional / ternary operator
cond ? a : b
==cond ? a : b== reads as "if cond is true, take a, else take b." It is the one-line, expression form of if/else and it builds exactly a 2-to-1 multiplexer .
cond — the selector wire.
? — "then take the left value…"
a — value chosen when cond is true.
: — "…otherwise take the right value…"
b — value chosen when cond is false.
Example: assign y = sel ? d1 : d0; — y is d1 when sel=1, else d0. That is a mux drawn in one line.
Definition Sensitivity list
==@(...)== after always lists the events that re-run the block. @(*) means "re-run whenever any input read inside changes" — the * = "all of them, automatically."
* protects you
If you list inputs by hand and forget one, the block won't react to it — the tool then remembers the old output, secretly building a latch you never wanted. @(*) catches every input so no path is forgotten. This is the parent's "incomplete sensitivity → accidental latch" mistake.
module and endmodule
==module opens the basic reusable block; endmodule== closes it. Everything describing one piece of hardware lives between them.
module and2 ( input a, input b, output y); // name + port list
assign y = a & b;
endmodule
module — keyword starting the block.
and2 — the block's chosen name.
( ... ) — the port list : the legs of the chip.
endmodule — keyword ending the block (no semicolon after it).
Definition Port declarations — direction + (width) + name
Each port inside the list states a direction , an optional width , and a name :
==input== — a wire coming in from outside.
==output== — a wire going out .
==inout== — a wire that can do both (see §11).
Example: input [7:0] data = "an 8-bit input bus named data." An output assigned inside an always block must also be declared reg (e.g. output reg q), because §7 says procedurally-assigned signals need storage-type.
Intuition Why wrap logic in a module at all?
A module is a box with labelled legs you can reuse and wire into bigger boxes — exactly like a chip on a board. The port list is the promise "these are my legs and which way each one points"; the body is "here is what I do." Without it, there is nothing to instantiate.
Definition inout and tri-state
An ==inout== port can be both driven and read. To share one physical wire among several drivers, each driver must be able to let go — drive z (high-impedance) when it is not its turn:
assign bus = enable ? data_out : 8'bz ; // drive data, or release to z
Here the ? : from §8b picks data_out when enable is high, or 8'bz (all-eight-wires-floating) when it is low — i.e. "let go of the bus."
Definition tri, tri1 and tri0
A ==tri== net is a wire you explicitly declare as possibly multiply-driven (tri-state). When exactly one driver is active it behaves like a normal wire.
==tri1== is a tri-state net with a built-in pull-up : when no driver is active (all at z), the wire rests at 1.
==tri0== is a tri-state net with a built-in pull-down : when nobody drives, it rests at 0.
So tri alone floats to z when idle; tri1/tri0 add a weak resistor that decides the idle value.
Intuition Why tri-state exists
On a shared bus, if two drivers push 0 and 1 at once you get a short (the simulator shows x). Tri-state solves this: only one driver is active, everyone else is at z (electrically invisible). The tri1/tri0 pull nets stop the wire from being an ambiguous z when everyone has let go. This is why z from §2b is not a curiosity — it is the language of shared buses.
Everything above was mostly Verilog spelling. VHDL says the same hardware things with different punctuation. Here is the full minimal vocabulary.
Definition entity + architecture (VHDL's module)
==entity== declares the interface : the block's name and its ports (the legs of the chip). It is the VHDL half of Verilog's module header.
==architecture== declares the behaviour : what happens inside — the VHDL half of the module body.
entity and2 is
port ( a, b : in std_logic ; -- input ports
y : out std_logic ); -- output port
end and2 ;
architecture rtl of and2 is
begin
y <= a and b; -- concurrent signal assignment
end rtl ;
port ( … ) lists ports; each has a direction (in/out/inout) and a type (std_logic = one four-state wire, std_logic_vector(7 downto 0) = an 8-bit bus). The concurrent <= here is VHDL's version of Verilog's assign.
Definition std_logic, downto and the word-operators
==std_logic== — VHDL's single wire type; it carries 0 1 and x/z-like states ('U','X','Z','-' …), matching §2b.
==downto== — VHDL's word for the Verilog : in [7:0]. A bus is std_logic_vector(7 downto 0); a slice is bus(7 downto 4), one bit is bus(3).
VHDL spells the gates as words : ==and, or, not, xor, nand, nor, xnor== — the same gates as Verilog's & | ~ ^, just written out.
Concatenation uses ==&== in VHDL (e.g. a & b glues buses) — note this is the opposite of Verilog, where & is AND. Context (and types) tell them apart.
Definition signal vs variable, and
:=
==signal — a real wire between processes; updated with <=== (concurrent, like Verilog non-blocking). Declared with signal s : std_logic;.
==variable== — lives only inside a process, updates immediately with ==:=== (like Verilog blocking =). Declared in the process header.
So VHDL's := ≈ Verilog =, and VHDL's <= ≈ Verilog <=. Same two flavours, different symbols.
Definition Instantiation, port map, generics
To use a block inside another, you instantiate it and connect its ports with a ==port map==:
U1 : and2 port map ( a => x, b => y, y => z ); -- name => wire
A ==generic== is a compile-time parameter (e.g. a bus width N) — VHDL's version of Verilog parameter. It lets one description build many sizes.
Recall Verilog ↔ VHDL cheat-map
Verilog module ::: VHDL entity + architecture
Verilog assign y = ... ::: VHDL concurrent y <= ...
Verilog [7:0] ::: VHDL (7 downto 0)
Verilog & | ~ ^ (gates) ::: VHDL words and or not xor