Wishbone · Module 2
Control Signals
Address and data are payload; they say what values are involved and nothing about what should happen to them. Control information is what makes a bus interpretable: a qualifier that says a request is real, a direction, lane enables, a completion and an error — each derived from a failure that occurs without it.
Chapter 2.3 moved values in both directions and leaned the whole time on signals that carry no data at all. valid decided whether a request existed. write decided the direction. byte_en decided which lanes counted. ready decided when it was over.
Those are the subject now, and the point is not a list of them. It is why each one has to exist, derived from what breaks in its absence.
Address and data say which values are involved. What tells the hardware what operation those values represent, and when they matter?
1. Deriving Control From Failure
Take a bus that has payload only — an address bus and a bidirectional data path — and try to use it. Each thing that breaks introduces exactly one control signal.
Failure 1: the target cannot tell a request from leftover wires.
Between transfers the address bus is still driving something — the last address, or whatever the initiator's register happens to hold. A target watching only the address sees a stream of values with no way to know which are requests. It acts on all of them, or on none.
Introduces: a qualifier. One signal meaning what is on the payload wires right now is a real request. In this module it is valid.
Failure 2: the target cannot tell a read from a write.
The same address is frequently both readable and writable, so direction cannot be inferred from it. Nor can it be inferred from whether the data wires are driven, because on a shared path somebody is always driving them.
Introduces: a direction. In this module, write.
Failure 3: the initiator cannot express a partial write.
Chapter 2.3 established this. Without lane enables every write is a full-word write, so a byte store becomes a read-modify-write — not slower but different, and on a peripheral register frequently destructive.
Introduces: lane enables. In this module, byte_en.
Failure 4: the initiator cannot tell when the target is finished.
Without this the interface must fix a latency, and Chapter 1.5 showed the fixed-latency system failing in every direction — five targets with four latencies, an initiator carrying special cases, and no structural fix for a timing problem available.
Introduces: a completion. In this module, ready.
Failure 5: a failed access is indistinguishable from a successful one.
An access to an unimplemented offset, a write to a read-only register, a target in reset — all complete. Software sees success and acts on nothing.
Introduces: an error indication. In this module, error.
Nothing on that list was chosen for elegance. Each is the minimum answer to a specific way an unqualified bus fails, and that is why this same set appears — under different names — in every on-chip bus you will meet.
2. The Two Categories, and Why the Split Matters
| Payload | Control | |
|---|---|---|
| Examples here | addr, wdata, rdata | valid, write, byte_en, ready, error |
| Carries | values | meaning |
| Between transfers | still driving something | must be deasserted |
| Meaningful when | control says so | always |
| Wrong value causes | wrong data | wrong operation |
| Width | wide, data-sized | narrow, usually one bit |
The row worth dwelling on is the third. Payload is allowed to be garbage between transfers — nobody is looking. Control is not, because control is what tells everyone whether to look. A qualifier that glitches high for one cycle manufactures a transfer that no initiator requested, out of whatever the payload wires happened to hold.
This is why control signals get the careful treatment in a design review. A wrong address produces a wrong access at a known moment. A wrong qualifier produces an access that nothing in the system asked for, and there is no software instruction to correlate it with.
3. RTL — A Complete Target Built on Control
Everything so far, in one block: a UART-style target that uses every control signal for something.
// ─────────────────────────────────────────────────────────────────────────
// uart_regs — a target that exercises every control signal in the Module 2
// teaching interface.
//
// PURPOSE. Show what each control signal actually causes in RTL, including
// the two cases a simpler target does not have: a write to a read-only
// register, and an access to an offset that does not exist.
//
// Register map (local offsets):
// 0x00 DATA write = transmit byte (lane 0); read = last received byte
// 0x04 STATUS read-only, hardware-updated
// 0x08 CTRL read/write, byte-writable
//
// Generic educational interface — NOT Wishbone signal names. Wishbone's own
// signals and their rules are Module 4's subject.
// ─────────────────────────────────────────────────────────────────────────
module uart_regs #(
parameter int unsigned AW = 8,
parameter int unsigned DW = 32
) (
input logic clk,
input logic rst_n,
// ── Control + payload from the initiator ─────────────────────────────
input logic sel,
input logic valid, // CONTROL: a request exists this cycle
input logic write, // CONTROL: direction
input logic [AW-1:0] addr, // PAYLOAD: local offset
input logic [DW-1:0] wdata, // PAYLOAD
input logic [3:0] byte_en, // CONTROL: which lanes participate
// ── Control + payload back to the initiator ──────────────────────────
output logic ready, // CONTROL: finished on this edge
output logic [DW-1:0] rdata, // PAYLOAD
output logic error, // CONTROL: finished unsuccessfully
// ── The hardware side ────────────────────────────────────────────────
output logic [7:0] tx_byte,
output logic tx_start,
input logic tx_busy,
input logic [7:0] rx_byte,
input logic rx_valid
);
localparam logic [AW-1:0] OFF_DATA = 'h00;
localparam logic [AW-1:0] OFF_STATUS = 'h04;
localparam logic [AW-1:0] OFF_CTRL = 'h08;
logic [DW-1:0] ctrl_q;
logic [7:0] rx_hold_q;
logic rx_ready_q;
// ── Qualification: both conditions, per Chapter 2.1 ──────────────────
logic access;
assign access = sel & valid;
// ── Classify the access. Three outcomes, and they are mutually
// exclusive by construction: legal, wrong offset, wrong operation.
logic addr_legal, write_to_ro;
always_comb begin
unique case (addr)
OFF_DATA, OFF_STATUS, OFF_CTRL: addr_legal = 1'b1;
default: addr_legal = 1'b0;
endcase
end
assign write_to_ro = access & write & (addr == OFF_STATUS);
// ── Completion and error. Both are CONTROL outputs owned by this block,
// and both are gated on `access` so this target can never complete a
// transfer belonging to somebody else.
//
// This target is never busy, so `ready` is combinational. A target that
// COULD be busy would hold `ready` low instead — the mechanism Chapter
// 2.5 builds the initiator side of.
assign ready = access;
assign error = access & (~addr_legal | write_to_ro);
// An errored access must NOT change state. Folding that into one term
// used by every write branch is what keeps the rule in a single place.
logic write_ok;
assign write_ok = access & write & addr_legal & ~write_to_ro;
// ── Sequential state ─────────────────────────────────────────────────
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ctrl_q <= '0;
rx_hold_q <= '0;
rx_ready_q <= 1'b0;
end else begin
// Hardware-driven: a newly received byte latches and sets the flag.
if (rx_valid) begin
rx_hold_q <= rx_byte;
rx_ready_q <= 1'b1;
end
if (write_ok && (addr == OFF_CTRL)) begin
if (byte_en[0]) ctrl_q[7:0] <= wdata[7:0];
if (byte_en[1]) ctrl_q[15:8] <= wdata[15:8];
if (byte_en[2]) ctrl_q[23:16] <= wdata[23:16];
if (byte_en[3]) ctrl_q[31:24] <= wdata[31:24];
end
// READ-TO-CLEAR. A successful read of DATA consumes the byte. This is
// a control-signal consequence people miss: the side effect is
// conditioned on a completing READ, so it must not fire on a write to
// the same offset, nor on a cycle where `access` is low.
if (access && !write && addr_legal && (addr == OFF_DATA))
rx_ready_q <= 1'b0;
end
end
// ── Transmit trigger. A write to DATA is an ACTION, not storage: the
// value goes to the shifter and a one-cycle start pulse is issued.
// `tx_start` is gated on lane 0 because the byte lives there.
assign tx_byte = wdata[7:0];
assign tx_start = write_ok & (addr == OFF_DATA) & byte_en[0] & ~tx_busy;
// ── Read path. Zero-extended, fully assigned on every path.
always_comb begin
rdata = '0;
if (access && !write && addr_legal) begin
unique case (addr)
OFF_DATA: rdata = {{(DW-8){1'b0}}, rx_hold_q};
OFF_STATUS: rdata = {{(DW-3){1'b0}}, rx_ready_q, tx_busy, ~tx_busy};
OFF_CTRL: rdata = ctrl_q;
default: rdata = '0;
endcase
end
end
endmoduleReading this module
Purpose. Every control signal is load-bearing here. Remove any one and a specific behaviour becomes inexpressible.
Interface contract. sel and valid together qualify; write directs; byte_en selects lanes; ready and error are this block's outputs and are asserted only inside an access.
Combinational decisions. Whether an access exists; whether the offset is legal; whether the operation is legal for that offset; whether a write may change state (write_ok); the read value; and the transmit trigger.
Sequential behaviour. Three pieces of state. ctrl_q changes only on a successful write, per lane. rx_hold_q and rx_ready_q are driven by hardware on receive and cleared by a bus read — the read-to-clear behaviour Chapter 1.2 warned about, now implemented.
Timing. Everything completes in the cycle it is requested. tx_start is one cycle wide because write_ok is, which matters: a held request would otherwise restart the transmitter every cycle.
Assumptions and simplifications. No wait states — a real UART would hold ready low while tx_busy, and Chapter 2.5 builds the other side of that. rx_valid is assumed synchronous to clk. There is no receive FIFO, no interrupt output, and ready is combinational from valid, which is the loop hazard Chapter 2.1 flagged.
How it could fail.
- Drop
~write_to_rofromwrite_okand a write to STATUS reports an error and changes state — the worst combination, because software was told it failed. - Drop the
!writecondition on the read-to-clear and a write to DATA also consumes the received byte. - Drop
~tx_busyfromtx_startand a write during transmission corrupts the byte in flight. - Make
tx_starta level instead of a pulse and every held request restarts the transmitter.
4. Waveform — A Generic Handshake
Control signals across four accesses
9 cyclesThe two cycles worth studying are 2 and 5.
Cycle 2 is the argument for a qualifier. addr still carries 0x08 and write is still high — the payload wires are indistinguishable from cycle 1's real write. The only thing that stops every target from writing again is valid being low. That is what qualification means, and it is why a bus without it does nothing rather than less.
Cycle 5 is the argument for an error that completes. The access is illegal, and it still asserts ready. An errored access that failed to complete would hang the initiator — turning a recoverable software fault into a dead system. Error and completion are different facts: one says it is over, the other says it did not work.
5. Verification — Properties About Control
// ─────────────────────────────────────────────────────────────────────────
// Control-signal properties for a target on the Module 2 teaching interface.
// These encode the rules THIS chapter states. They are not any bus's
// protocol rules.
// ─────────────────────────────────────────────────────────────────────────
module control_checker #(
parameter int unsigned AW = 8,
parameter int unsigned DW = 32
) (
input logic clk,
input logic rst_n,
input logic sel,
input logic valid,
input logic write,
input logic [AW-1:0] addr,
input logic ready,
input logic error,
input logic [DW-1:0] ctrl_q, // white-box: the writable register
input logic [DW-1:0] status_shadow // white-box: expected read-only value
);
default disable iff (!rst_n);
// P1 — completion only inside a qualified access. The ownership rule from
// Chapter 2.1, restated now that `valid` has been derived properly.
property p_ready_qualified;
@(posedge clk) ready |-> (sel && valid);
endproperty
a_ready_qualified : assert property (p_ready_qualified)
else $error("ready asserted outside a qualified access");
// P2 — an error is a KIND of completion, never an alternative to it. A
// target that raised error without ready would hang the initiator,
// which is the cycle-5 argument in Section 4.
property p_error_implies_ready;
@(posedge clk) error |-> ready;
endproperty
a_error_implies_ready : assert property (p_error_implies_ready)
else $error("error asserted without completing the transfer");
// P3 — a read-only register is not modified by a bus write. Expressed on
// the state rather than on the interface, because that is where the
// damage would be.
property p_ro_immutable;
@(posedge clk) $changed(status_shadow) |-> !(ready && write);
endproperty
a_ro_immutable : assert property (p_ro_immutable)
else $error("read-only state changed on a completing write");
// P4 — the writable register changes ONLY on an accepted, non-errored
// write. This catches the `write_ok` term being wrong in either
// direction: a write that should have been rejected and landed, or a
// register that moves when no write occurred at all.
property p_ctrl_only_on_good_write;
@(posedge clk) $changed(ctrl_q) |-> $past(ready && write && !error);
endproperty
a_ctrl_only_on_good_write : assert property (p_ctrl_only_on_good_write)
else $error("ctrl_q changed without an accepted, error-free write");
endmoduleWhy these four. P1 and P2 are interface rules; P3 and P4 are state rules, and the pairing is deliberate. An interface-only property cannot catch a target that reports an error and changes state anyway — the worst failure in Section 3's list, because software has been told the access failed.
$past in P4 refers to the previous clock cycle's values, which is what makes the property expressible: ctrl_q changes at an edge, and the conditions that permitted it were true before that edge.
What is not asserted. Nothing about how long a target may take, because this target is never busy. That is the next chapter.
6. Failure Modes and Discriminating Evidence
Symptom: registers change with no corresponding software access.
Candidates. A target not gating on valid, so it acts on stale payload between transfers. A qualifier that glitches. A held request that a target re-applies every cycle.
Discriminating evidence. Trigger on the register changing and look at valid in that cycle. Low means the target is not qualifying. High for many consecutive cycles with one intended access means the target is re-applying a held request — it needs edge detection, or the initiator needs to drop valid after acceptance.
Property that catches it. P4.
Symptom: a write reports an error and takes effect anyway.
Candidates. The write-enable term does not include the error condition.
Discriminating evidence. Access a read-only offset with a write; check error and then read the register back. Both true is conclusive.
Likely RTL location. The write_ok term — specifically a missing ~write_to_ro or addr_legal.
Property that catches it. P3 and P4 together.
Symptom: the system hangs on an access to a non-existent offset.
Candidates. The target raises error instead of ready rather than with it.
Discriminating evidence. Probe both. error high with ready low is the bug, and it is visible in a single cycle.
Property that catches it. P2.
Symptom: a received byte disappears when a debugger is attached.
Candidates. Read-to-clear on the DATA register, consumed by the debugger's register view.
Discriminating evidence. Detach the debugger and see whether the problem stops. Then check whether the read-to-clear is correctly gated on !write and on access.
Correct conclusion: usually not a bug at all, but the documented behaviour interacting with a tool — which is why a register map must mark read-to-clear registers explicitly.
Symptom: the UART transmits the same byte repeatedly.
Candidates. tx_start is a level rather than a pulse, so a held request restarts it every cycle.
Discriminating evidence. Put valid, ready and tx_start on one waveform. A tx_start wider than one cycle is conclusive.
Likely RTL location. The tx_start expression — it must be derived from the one-cycle acceptance term, not from valid alone.
7. Common Mistakes
"The address bus tells the target what to do."
Wrong mental model: the address is the request.
Concrete failure: a target acts on whatever the address bus holds between transfers, writing registers nobody asked to write.
Observable evidence: register state changing with no software access, often correlated with the previous access's address.
Correct model: the address is payload. Qualification is what turns a value on a wire into a request, and it is a separate signal by necessity.
"Error and completion are alternatives."
Wrong mental model: a transfer either succeeds, or errors, and those are two different endings.
Concrete failure: a target raises error and never raises ready. The initiator waits forever. A recoverable software fault becomes a dead system.
Observable evidence: a reproducible hang on a specific bad address, with error high in the waveform.
Correct model: error is a property of a completion, not a substitute for it. Every access ends; error says how it ended.
"A control signal can be held for as long as convenient."
Wrong mental model: holding a request steady is harmless.
Concrete failure: a target that treats the request as level-sensitive applies it every cycle — writing a register repeatedly, or restarting a transmitter mid-byte.
Observable evidence: an action that happens many times for one software access; a transmitter that never finishes a byte.
Correct model: the interface must define when a request takes effect. Here it is the single edge where valid and ready are both high, and every side effect must be derived from that term rather than from valid alone.
"Read-to-clear is a peripheral quirk, not a bus concern."
Wrong mental model: side effects live inside the peripheral and do not interact with control signals.
Concrete failure: the clear fires on a write to the same offset, or on a cycle where the target was not selected — consuming an event nothing read.
Observable evidence: receive data lost when an unrelated register is written.
Correct model: a side effect is conditioned on a completing access of a particular direction, which is a control-signal expression. Getting it wrong is a control bug wearing a peripheral costume.
8. Interview Reasoning
Because the wires are never blank, so a target has no way to distinguish a request from residue.
Between transfers the address bus is still driving something — the last address, or whatever the initiator's register holds. A target watching only payload sees a continuous stream of values and cannot tell which represent requests. It either acts on all of them or on none, and both are useless.
A qualifier is the minimum fix, and everything else follows the same pattern: direction cannot be inferred from an address, because the same address is usually both readable and writable; partial writes cannot be expressed without lane enables; completion cannot be inferred without a fixed latency, which reintroduces every problem Module 1 catalogued; and a failed access cannot be distinguished from a successful one without an error indication.
The framing that distinguishes a strong answer: payload says what values are involved, control says what operation they constitute. Payload without control is not an incomplete bus — it is a bus that cannot do anything, because nothing on it can be interpreted.
9. Understanding Check
10. What's Next
Control has been derived rather than listed: a qualifier because payload wires are never blank, a direction because an address cannot carry one, lane enables because partial writes must be expressible, a completion because latency must be local, and an error because failure must be distinguishable from success.
Each has been treated as an individual signal. But the waveform in Section 4 shows something none of them describes alone: cycle 1 and cycle 3 are not five independent signal events each — they are two operations, each with a beginning, a middle and an end.
What turns a collection of address, data and control signals into one coherent operation — and what must stay true for as long as that operation is in flight?
Chapter 2.5 — Bus Transactions makes the transaction the unit: its lifecycle, what the initiator must hold stable while waiting, what a target that is genuinely busy forces the other side to build, and how an operation ends when it does not succeed. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
FPGA Design Challenges
Six peripherals and two masters on one FPGA is where on-chip integration stops being theory. The decode that must be exhaustive and one-hot, the read multiplexer that grows with every target and carries the critical path, the latency and reset conventions that refuse to agree, and the point at which the fabric rather than the peripherals starts failing timing.
- Related topic
Address Space
An address is a number until a decoder turns it into a target selection and a local offset. Range comparison and mask comparison are different engineering choices with different costs; exhaustive, mutually exclusive decode is a property to be proved rather than assumed; and a flat decoder's critical path is what eventually forces a hierarchy.
- Related topic
Bus Transactions
A transaction is the unit of bus work: one beginning, one ending, and an interval in between during which the request must not move. Making waiting expressible is what lets a slow target share a bus with a fast one, and it is what turns an initiator from a wire into a state machine with real failure modes.
- Related topic
The Master Interface
A Wishbone master owns the address, the direction, the write data, the byte selects and both qualifiers, and must hold them until a termination arrives. What it is deliberately not allowed to know matters as much as what it drives — a master that knows the address map or a slave's latency has been coupled to one system.
Standards & specifications
- Governing standard
- Wishbone SoC Interconnection Architecture (OpenCores)(opens OpenCores in a new tab)
Defines the Wishbone signal set, the bus cycles built from it and the interface rules a portable IP core must follow. It deliberately leaves interconnect topology, address map and arbitration policy to the integrator, so those are system decisions rather than requirements of the specification.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the Wishbone curriculum.
