CHAPTER 01
Four numbers on the bench
The first result is one. Four inputs, four weights, a bias, and a small circuit have spent several clock edges arriving at an answer you could work out on the back of an envelope.
That is a good place to begin. If the answer were a paragraph of fluent prose, you would have trouble deciding which part of the machine had gone wrong. With four numbers, you can follow every multiplication. You can point to the register that holds the partial sum. You can deliberately stop the incoming data and check that the circuit waits.
Our circuit computes bias + x0*w0 + x1*w1 + x2*w2 + x3*w3. It accepts signed eight-bit inputs and weights, accumulates without overflow under its stated limits, and returns a signed eighteen-bit result. A small demonstration trains a classifier in Python and sends its arithmetic through the same design. The complete source package accompanies this book.
The project begins with a specification and ends with a design you can simulate, synthesize into generic logic, and adapt to a physical implementation. The edition's reported tests were performed in simulation. No FPGA board was programmed and no silicon was fabricated for the book. The later chapters explain those steps and the evidence each would add.
“From scratch” needs a boundary. You will write the arithmetic and control logic. You will use a simulator, a synthesis tool, and existing descriptions of manufacturing processes. Building those tools, refining silicon, and constructing a fabrication plant are different projects. Even a small chip is made through a division of work.
What makes the circuit relevant to AI
A weighted sum is a common operation in learned models. The weights determine how much each input contributes; a bias shifts the result. A decision or activation function may follow. Repeating and arranging these operations gives much larger networks their arithmetic workload.
The hardware does not know whether its weights were trained, chosen by hand, or typed incorrectly. It follows the bits at its inputs. The useful connection to machine learning comes from the surrounding specification: what the inputs mean, how the weights were obtained, which numerical format they use, and how the output will be interpreted.
That surrounding work matters even when the multiplier is perfect. Feeding a temperature measured in one unit to a model trained in another can produce a wrong decision with flawless arithmetic. A chip's output is only as interpretable as its interface and data preparation.
We will keep those questions visible with a tiny synthetic example. Four integers represent four pixels. The task is to decide whether the left column is brighter than the right. Nobody needs an accelerator for this task. Its advantage is that all 256 possible inputs in the demonstration can be inspected, and the learned arithmetic can be checked independently.
Put the result ahead of the toolchain
Read the numerical contract before installing anything. Then run the reference calculation. Only after that should you compile the circuit. This order gives each tool a question to answer.
The reference answers what value should be produced. Simulation asks whether the clocked description produces it under the tested conditions. Synthesis asks how the description can be expressed as logic. Physical design asks where that logic and its wires can go in a particular technology. Manufactured hardware adds measurements that the earlier representations could only predict.
Keep the files from each stage. A passing simulation log and a rendered layout image are different artifacts. Name them so that six weeks later you can tell which source revision produced them.
You will need some comfort with code, binary numbers, and a terminal. The hardware language will be explained through the example. There is no requirement to memorize a processor architecture or begin with an operating system. A circuit small enough to understand completely is already a substantial first build.
CHAPTER 02
Give every bit a meaning
Eight bits can hold 256 patterns. Those patterns do not announce whether they represent a positive integer, a negative integer, a letter, or eight independent switches. The interface has to say.
Our inputs use two's-complement signed integers. Their range is minus 128 through plus 127. For a negative value, the same eight bits interpreted as unsigned would mean something else. The pattern 11111111 means minus one under our contract and 255 under an unsigned contract. Both interpretations are internally consistent. Mixing them is the bug.
The bias uses sixteen signed bits, from minus 32,768 through plus 32,767. The output has eighteen signed bits. Its range is minus 131,072 through plus 131,071. We choose that width by bounding the arithmetic, rather than by matching the familiar size of a software integer.
Work out the extremes first
The largest positive product of two signed eight-bit values is (-128)*(-128), which equals 16,384. The most negative product is (-128)*127, which equals minus 16,256. Both fit in sixteen signed bits.
Four maximum positive products plus the largest bias give 98,303. Four most-negative products plus the smallest bias give minus 97,792. The complete result therefore fits comfortably in eighteen signed bits. The intermediate partial sums fit too: with at most four terms, none can exceed the corresponding bound for all four extreme terms and the bias.
This calculation is specific to four terms. If you change the counter to process a thousand terms but leave the accumulator untouched, the old argument no longer applies. The circuit may compile, simulate a friendly example, and fail only when the data reaches a combination you did not try.
| Quantity | Signed width | Range relevant to this design |
|---|---|---|
| Input or weight | 8 bits | −128 to 127 |
| One product | 16 bits | −16,256 to 16,384 |
| Bias | 16 bits | −32,768 to 32,767 |
| Complete four-term sum | 18 bits | −97,792 to 98,303 |
The last row is the reachable arithmetic range, not the full representable range of an eighteen-bit integer. Leaving some unused headroom is acceptable. Claiming a smaller width without checking it would be less economical than it looks.
Widen a negative number correctly
When a signed value grows wider, its sign must be preserved. For two's-complement numbers, sign extension repeats the top bit into the added positions. An eight-bit minus one becomes a wider row of ones. Adding zeroes on the left would turn it into a positive value.
The circuit explicitly sign-extends each sixteen-bit product to eighteen bits before addition. It also sign-extends the bias when loading the accumulator. These operations cost no arithmetic in the ordinary sense; the new high bits are connected to the existing sign bit.
Explicit widths make the example easier to review. Hardware languages have rules for expression sizing and signedness, and those rules can interact in ways that surprise a programmer accustomed to a single machine integer type. A named intermediate wire gives the product a clear width and interpretation before the next operation uses it.
A number format is part of the model
Our worked classifier uses integers throughout. A larger model may begin with real-valued parameters. Representing them with integers requires a quantization scheme: scales, possibly zero points, rounding, clipping, and compatible rules for accumulated values. Simply casting every weight to an integer is not a complete scheme.
The paper by Jacob and colleagues is a primary source for an integer-inference approach and training procedures intended to preserve accuracy. Our four-term engine implements a much narrower arithmetic primitive; it does not reproduce that paper's full quantization method.
For an original experiment, write a numerical example that includes every conversion. If a real value of 0.5 becomes the integer 64, explain the scale. If two scaled values are multiplied, account for the resulting product scale. Decide how the next layer will interpret the accumulated result before reducing its width.
The numerical contract belongs beside the port list. Without it, the wiring may be correct while two parts of the system disagree about every value that passes between them.
CHAPTER 03
A clock is an agreement
A Python loop performs four multiplications in a sequence chosen by the program and runtime. Our circuit contains a multiplier that can respond whenever its inputs change, plus registers that update on clock edges. The distinction becomes visible as soon as you ask what happens while the input source is waiting.
The multiplier's output is combinational: it is determined by the present input values after the circuit's propagation delay. The accumulator is sequential state. It retains a value between clock edges and loads a new one when the control conditions permit.
The clock does not make the multiplication instantaneous. It establishes when a register is supposed to sample a result that has had enough time to settle. Physical timing checks will eventually determine whether the chosen clock period allows that to happen in the selected implementation.
The transaction
A transaction starts on a rising clock edge when start is high and busy is low. That edge copies the bias into the accumulator, sets the term index to zero, and raises busy. It does not consume a product, even if valid is also high.
While busy, each rising edge with valid high consumes one input-and-weight pair. An edge with valid low consumes nothing. The partial sum and term index remain unchanged. On the fourth accepted pair, the circuit writes the final result, lowers busy, and raises done for one clock cycle.
The result remains available until another completed transaction replaces it or reset clears it. Starting a new transaction does not immediately erase the previous result. A consumer must therefore use completion status to distinguish a new value from an old one.
| Edge | Control presented | Action after the edge |
|---|---|---|
| 0 | Start, idle | Load bias; become busy |
| 1 | Valid pair 0 | Add first product |
| 2 | Valid low | Wait; keep partial sum |
| 3 | Valid pair 1 | Add second product |
| 4 | Valid pair 2 | Add third product |
| 5 | Valid pair 3 | Publish result; pulse done |
The gap at edge two is intentional. It costs elapsed time but should not change the answer. This simple behavior gives the host room to deliver data without pretending it can always supply a new pair every cycle.
Decide awkward cases now
A start request while busy is ignored. A valid input while idle is ignored. Reset has priority over everything and clears the accumulator, result, counter, and status. The reset is synchronous and active high: it must be asserted across a rising clock edge to take effect.
These are design choices, not universal meanings of signal names. Another block may queue a new request or use a ready/valid handshake with different rules. Integration requires reading the contract instead of assuming that familiar names imply familiar timing.
Our host should pulse start for a single accepted edge. If it leaves start high until the block becomes idle again, it can unintentionally begin another transaction. There is no hidden request identifier in this interface to distinguish a repeated level from a new command.
A done pulse can also be missed by a slow observer. A synchronous consumer can sample it on the appropriate clock. A person reading a status register through a slow interface needs a persistent indication. The byte-bus wrapper introduced later latches completion until another accepted start clears it.
Test at a different instant from the design
A testbench that changes inputs at the same instant the circuit samples them can create a simulation race. Our tests drive new values on falling edges and inspect outputs shortly after rising edges. That arrangement gives the test an unambiguous view of what was presented and what was registered.
The delays in the testbench are simulation machinery. They are not a proposed circuit that waits a nanosecond by executing a statement. The synthesizable module contains no delay commands. Its timing will come from gates, wires, clock distribution, and constraints.
Before reading the Verilog, trace one transaction with a pencil. Write the bias, the four products, and the partial sum after each accepted edge. If you cannot say when the result should appear, a waveform viewer will give you more lines without resolving the uncertainty.
CHAPTER 04
Write the arithmetic twice
Start with the Python reference. Python adds four products using integers that do not overflow at the widths in this project. It also checks the input contract. That keeps a mistaken test vector from silently becoming a different experiment.
def dot4(xs, ws, bias=0):
if len(xs) != 4 or len(ws) != 4:
raise ValueError('Exactly four inputs and weights are required')
if any(type(v) is not int or not -128 <= v <= 127
for v in [*xs, *ws]):
raise ValueError('Inputs and weights must be signed 8-bit integers')
if type(bias) is not int or not -32768 <= bias <= 32767:
raise ValueError('Bias must be a signed 16-bit integer')
return bias + sum(x*w for x, w in zip(xs, ws))
For inputs [3, -2, 5, 1], weights [4, 7, -1, 2], and bias six, the products are twelve, minus fourteen, minus five, and two. Their sum is minus five. Adding the bias gives one.
Write the same calculation as a sequence of accumulator states: six before the first pair, eighteen afterward, then four, then minus one, then one. Those values become a short diagnostic trace. If the final answer is minus one, the circuit may have published the previous accumulator before including the fourth product.
That particular mistake is easy to make with clocked assignments. The Verilog will handle it explicitly by naming the next sum and assigning that value both to the accumulator and, on the final term, to the result.
Keep the reference independent
The reference function does not copy the hardware's counter or state machine. It calculates the mathematical result directly. If both implementations used the same mistaken state update, agreement between them would be weaker evidence.
Independence does not require a second programming language for every test, but it benefits from a different route to the answer. A direct sum can check a serial accumulation. An exact integer calculation can check a limited-width implementation. A manually worked extreme can check a randomly generated test collection.
The generator writes each vector as ten decimal integers: four inputs, four weights, the bias, and the expected result. Plain text makes a failure easy to inspect. When the simulator reports a vector number, you can recover the values without a proprietary viewer.
Five directed examples cover extreme positive and negative accumulation, zero products with a negative bias, and the small hand calculation. An exhaustive sweep then exercises all 65,536 possible pairs of signed eight-bit operands with the other terms set to zero. Finally, 2,000 seeded random vectors exercise complete four-term sums and varied biases.
This totals 67,541 vectors. The exhaustive part covers the single-product input space, not every possible complete transaction. There are far too many combinations of eight independent operands and a bias for that claim. Stating exactly what was exhausted avoids turning a useful test into an inflated one.
Reproduce the inputs
The random generator uses a fixed seed. That gives the edition a stable test set. If you change the seed, record it; if a failure appears, retain the failing vector even after fixing the bug. It should become a directed regression case.
Do not keep rerolling tests until they pass. A random failure is evidence about a reproducible input, not bad luck to be discarded. Reduce the example until you understand which property triggers it. A negative operand, a stall before the last term, or a reset during a transaction may be enough.
The supplied tests check more than the final number. They inspect busy and done, verify that stalls do not complete an operation, interrupt work with reset, and confirm that the old result remains readable after the done pulse disappears. A correct arithmetic result at the wrong time can still break the consumer.
Keep the reference small enough to audit. A reference model that includes an entire unexamined framework can be appropriate later, but this example has no need for one. Four multiplications and an addition are transparent enough to give the hardware a demanding test without making the expected answer mysterious.
CHAPTER 05
Read the circuit
The module has three pieces of stored arithmetic state: a two-bit index, an eighteen-bit accumulator, and the eighteen-bit result. Busy and done add two status bits. The multiplier itself has no register in this version. Its combinational output feeds the addition that precedes the accumulator.
Here is the complete core. The downloadable dot4.v contains the same design, with its interface comments.
// SPDX-License-Identifier: MIT
// Four signed 8-bit products plus a signed 16-bit bias.
// All inputs are synchronous to clk. rst is synchronous, active high.
module dot4 (
input wire clk, input wire rst,
input wire start, input wire valid,
input wire signed [7:0] x, input wire signed [7:0] w,
input wire signed [15:0] bias,
output reg busy, output reg done,
output reg signed [17:0] result
);
reg [1:0] index;
reg signed [17:0] acc;
wire signed [15:0] product = x * w;
wire signed [17:0] extended_product = {{2{product[15]}}, product};
wire signed [17:0] next_acc = acc + extended_product;
always @(posedge clk) begin
if (rst) begin
index <= 0; acc <= 0; result <= 0;
busy <= 0; done <= 0;
end else begin
done <= 0;
if (!busy) begin
if (start) begin
acc <= {{2{bias[15]}}, bias};
index <= 0; busy <= 1;
end
end else if (valid) begin
acc <= next_acc;
if (index == 3) begin
result <= next_acc;
busy <= 0; done <= 1;
end else index <= index + 1'b1;
end
end
end
endmodule
Start with the wires above the clocked block. product has a fixed signed width of sixteen bits. extended_product repeats its sign bit twice. next_acc adds that widened value to the current accumulator. These expressions describe logic that is present at the same time, rather than software instructions waiting their turn.
The always block describes updates at a rising clock edge. Its nonblocking assignments, written <=, arrange for state to change after the right-hand values have been evaluated for that edge. Consequently, two assignments using acc both see the old accumulator unless they explicitly use a combinational expression for the new value.
On the fourth term, result <= next_acc is essential. Replacing it with result <= acc would return the sum before the last product. The hand trace from the previous chapter would end at minus one instead of one. This is a useful deliberate mutation to try in a copy of the project: the test should fail for a reason you can explain.
Read the control from the outside inward
Reset is checked first. In ordinary operation, done is cleared each cycle. An accepted fourth pair overrides that default with a one. This produces the intended one-cycle completion pulse.
When idle, the only meaningful command is start. When busy, the only data event is valid. The structure makes a busy start harmless: it never reaches the idle branch. It also makes a start-and-valid combination while idle load only the bias.
The index takes the values zero through three. Those values identify which accepted pair is being processed, not how many clock cycles have elapsed. Stalls leave it unchanged. The final pair is recognized while the index is three; there is no need to store a value of four after completion.
Notice that the result is not assigned on every ordinary cycle. A clocked register retains its value when no branch updates it. That is how the old answer remains available. The same omission in a combinational block can infer unwanted storage, so the context matters: this block is explicitly clocked.
Find the expensive path
In this first architecture, input data passes through multiplication and addition before reaching the accumulator register. The feedback path from the accumulator passes through the adder. A synthesis and timing flow can describe how those paths are implemented in a chosen technology.
Adding a register between multiplication and addition might allow a shorter clock period. It would also change the timing contract. The data and its valid indication would need to advance together, and the last term would need to be identified at the correct pipeline stage. A register is not a free performance switch.
For now, the short core makes debugging easier. There is one accepted pair per active cycle, one partial sum to inspect, and no overlapping transaction. A more elaborate pipeline should earn its complexity through a measured requirement.
You can make the code shorter by collapsing wires and conditions. Resist that temptation while learning. The named product and next sum provide natural places to inspect a waveform. The explicit signed extensions expose the numerical assumptions. Keep those intermediate signals available while checking the design.
CHAPTER 06
Make the simulator disagree
The testbench must check values and timing against the specification and stop when they disagree. Inspecting a waveform is useful, but it can miss an error the assertions catch.
The supplied testbench stops with an error on the first mismatch. It prints which vector failed, what the circuit returned, and what the independent reference expected. That turns a large run into a specific problem you can investigate.
Install Python 3 and Icarus Verilog, following their installation instructions for your operating system. The edition was tested with Python 3 and Icarus Verilog 12.0 on Linux. A newer release may work as well; keep its version in your own record.
Unpack the workbench and run these commands from its directory:
python3 make_vectors.py
iverilog -g2012 -s tb_dot4 -o sim dot4.v tb_dot4.v
vvp sim
The first command creates the text vectors. The second compiles the design and testbench with the testbench selected as the simulation's top module. The third runs the compiled simulation. The successful run reports 67,541 vectors followed by the protocol checks it exercised.
A compile that produces no error has not performed the tests. A simulator that starts but cannot find vectors.txt has not tested the arithmetic either. Read the final result rather than treating activity in a terminal as evidence of completion.
Arrange a failure you understand
Make a copy of the directory. Change the final result assignment from next_acc to acc, recompile, and run again. The expected failure establishes that the test can detect a missing last term. Restore the original line afterward.
Next, replace sign extension with zero extension in the copy. Positive examples may still work. Negative products should reveal the error. This exercise explains why a single attractive demonstration is insufficient: some mistakes occupy only a region of the input space.
You can also remove the condition that requires valid before consuming a pair. The stall checks should catch the resulting early completion or incorrect sum. Each mutation connects a requirement to evidence that the test can enforce it.
Do not publish the mutated files under the original test report. A result belongs to a particular revision. The simplest working practice is to commit the passing version before experimenting and retain the mutation on a separate branch or in a clearly named copy.
Read the first wrong cycle
When a test fails, compare the partial sums with the hand trace. If the first product is already wrong, inspect input values, signedness, and multiplication width. If products are correct but the running sum goes wrong, inspect extension and accumulator updates. If the value is correct but appears late, inspect control and observation timing.
A waveform dump can help. Add a small simulation-only block to the testbench that calls $dumpfile and $dumpvars, then run a reduced set of vectors. Dumping every signal for every exhaustive case creates a large file that can obscure the simple example you need.
The reduction should preserve the failure. Keep the original failing vector, remove unrelated cases, and shorten the sequence only when the mismatch remains. For a state-dependent bug, the previous transaction may matter. A standalone vector that passes does not disprove the earlier failure.
Unknown values also deserve attention. In a four-state simulator, x can indicate uninitialized or conflicting information. The test uses comparisons that treat unknown outputs as failures. Replacing them with a comparison that quietly propagates an unknown condition can accidentally make a test less demanding.
Know what this run cannot measure
The testbench's clock alternates every five simulation nanoseconds. That does not establish that the manufactured design can run at 100 megahertz. The RTL has no technology-specific delays, and the testbench has not checked setup or hold timing in a fabricated process.
Likewise, exhaustive operand testing does not detect every physical fault, every possible control sequence, or every integration error. It checks the stated arithmetic and selected protocol properties in the simulated representation. That is substantial evidence, provided the description stays precise.
The next stages should add different evidence. Synthesis checks the translation into logic. Timing analysis uses constraints and technology data. A board test exercises pins, clocks, resets, and power. Repeating the same passing RTL test many more times cannot substitute for those missing observations.
CHAPTER 07
Teach a model small enough to inspect
Arrange four integers as a two-by-two image. The first and third belong to the left column; the second and fourth belong to the right. Each value ranges from zero through three. There are exactly 256 possible images.
The label is one when the left column's sum exceeds the right column's sum, and zero otherwise. This is an invented teaching task. Its labels come from that rule, not from human annotation, photographs, or a published benchmark. We use it because the model's job is easy to check.
The rule is linearly separable. A weighted sum with an appropriate threshold can express it. A perceptron training loop can therefore provide a small example of learning weights from labeled cases without introducing a large numerical framework.
Separate the learning from the circuit
The supplied train_demo.py enumerates the images, shuffles them with a fixed seed, and takes 192 for training and 64 for a held-out check. It begins with zero weights and zero bias. For each training sample it predicts a class from the current score.
If the prediction is wrong, the loop adjusts each weight by the input value multiplied by the signed classification error, and adjusts the bias by that error. It repeats passes through the training set until a pass makes no mistakes or the explicit iteration limit is reached.
The hardware plays no role in that training loop. It receives the resulting integer weights afterward. This is inference: evaluating a model whose parameters have already been selected. Training hardware would need to support a different workload, including the update procedure and its storage requirements.
Run the demonstration with:
python3 train_demo.py
vvp sim +vectors=demo-vectors.txt
For the edition's fixed ordering, training converged in four passes with weights [10, -11, 10, -11] and bias minus three. The held-out check classified all 64 examples correctly. The script then generated arithmetic vectors for all 256 images, and the RTL matched every expected score.
Those observations support a narrow claim: the supplied synthetic task and arithmetic implementation agree under the recorded setup. They say nothing about natural-image recognition. Four pixels with values from zero to three do not contain the ambiguities, noise, or variety of a camera feed.
Inspect the learned rule
The learned weights give a score of ten times the left sum, minus eleven times the right sum, minus three. The classifier predicts one when that score is at least zero.
Let the column sums be L and R, each between zero and six. If L equals R, the score is minus R minus three, which is negative. If L is less than R, it is more negative still. If L exceeds R by at least one, then R can be at most five, and the smallest score at a one-unit lead is seven minus R, which remains positive.
That short argument explains why the fitted rule works over the complete demonstration domain. It also explains why extrapolation needs care. For much larger column sums, the unequal coefficients could eventually make a one-unit left lead score negative. The training domain was bounded; the learned coefficients did not recover a universally equivalent formula over all integers.
This is a useful failure to anticipate. Hardware can preserve the learned score perfectly while an application feeds it values outside the range for which the model was checked. An input-range check is therefore part of the complete system, even when every value still fits in eight bits.
Keep scores before reducing them to labels
The workbench compares exact scores, not just classifications. Two arithmetic implementations might disagree numerically yet land on the same side of zero for every easy sample. Checking only labels would miss the discrepancy until a sample approached the threshold.
The exact-score test separates hardware correctness from model quality. First ask whether the circuit evaluates the intended model. Then ask whether that model solves the application problem. A poor model and a faulty circuit are different problems with different remedies.
For an extension, change the synthetic label to exclusive-or on two binary inputs and try the same single perceptron. It cannot separate that pattern with one affine decision boundary. The iteration limit should expose the failure to converge instead of encouraging an endless training run. A hidden layer or a different feature representation would change the model, and would give the accelerator a new workload to support.
CHAPTER 08
Get the answer through eight wires
The arithmetic core has a convenient interface for simulation: separate ports for inputs, weights, bias, and a wide result. A small physical project may not have enough pins to expose all of them at once.
One response is to send a few bits at a time. Our dot4_bus.v wrapper uses an eight-bit data input, a three-bit write address, a write strobe, and a two-bit read selector. It returns eight output bits. Clock and reset remain separate. Every input is synchronous to the same clock.
The wrapper stores a bias and one sample. Writing a weight supplies the second half of a pair and tells the core to consume it. Three read selections expose the signed result; the fourth exposes status. This is a small custom protocol, not an implementation of a standard processor bus.
| Write address | Meaning |
|---|---|
| 0 | Load low bias byte while idle |
| 1 | Load high bias byte while idle |
| 2 | Store the next signed input sample |
| 3 | Submit the data byte as its signed weight |
| 4 | Request start |
| 5–7 | No operation |
To perform a transaction, write the two bias bytes, issue start, then alternate sample and weight writes for four pairs. The host must know whether the core is busy before trying to start. Bias writes while busy are ignored. A start while busy is ignored as well.
The eight-bit pattern sent for a negative sample is its two's-complement representation. The data bus itself is merely eight wires; the sample register and the core's weight input give those wires a signed interpretation.
Make completion persist
The core's done signal lasts one clock cycle. The wrapper records it in a finished bit. A new accepted start clears that bit; reset clears it too. Because the wrapper samples the core's registered pulse, finished becomes visible one clock edge after the core completes.
Read selection three returns busy in bit zero and finished in bit one, with the other bits zero. During an ordinary transaction it reads one. After completion has been latched it reads two. There is a brief cycle after the core lowers busy and before the wrapper raises finished in which both are zero. The host should wait for finished when it wants the new result.
This delay is part of the protocol. A host that interprets “not busy” as “the finished flag must already be set” would disagree with the implementation even though both bits behave as documented.
Read selections zero and one expose the low and middle result bytes. Selection two exposes the top two result bits with the sign extended across the remainder of the byte. Combining those three bytes yields a signed twenty-four-bit representation of the eighteen-bit answer.
For a negative result, preserve that signed interpretation in the host. A microcontroller that combines the bytes into an unsigned integer and prints it directly will display a large positive number. The wire-level answer can be correct while the display is wrong.
Test the adapter as a separate design
Compile the wrapper testbench with both modules:
iverilog -g2012 -s tb_bus -o sim-bus dot4.v dot4_bus.v tb_bus.v
vvp sim-bus
vvp sim-bus +vectors=vectors.txt
The first run uses the 256 synthetic image cases. The second uses the full 67,541 arithmetic vectors. The edition passed both. The test also tries bias and start commands while busy, then checks that they did not alter the answer.
The wrapper adds cost. Each pair takes separate sample and weight writes, and the bias and start require their own commands. Even before adding idle gaps, a complete transaction needs eleven writes. An eight-bit interface saves pins by spending transfers.
A later version might retain weights internally and accept only samples, or use a wider bus. Another might stream a long vector and return a single result. Choose among those options by counting actual transfers and identifying where the data already lives.
This wrapper does not synchronize an unrelated external clock domain. Its strobe, address, and data must meet the receiving clock's timing requirements. A host that changes them at arbitrary times needs a properly designed crossing or a controlled clocking arrangement. Adding a couple of registers to only the strobe does not, by itself, guarantee that the associated multi-bit data is captured coherently.
CHAPTER 09
Turn the description into gates
Synthesis changes the representation of the design. The input is a hardware description; the output is a network of logic and storage elements. A multiplication operator may become many smaller operations, or map to a dedicated resource when the target provides one.
For the first inspection, use Yosys. The command below reads only the core, selects it as the top module, performs generic synthesis without the ABC mapping stage, checks the result, reports its contents, and writes a Verilog netlist.
yosys -p 'read_verilog dot4.v; synth -top dot4 -noabc; check; stat; write_verilog -noattr dot4-netlist.v'
The edition used Yosys 0.33. Its generic result contained 689 cells, including 40 register bits and a collection of AND, OR, XOR, NOT, and multiplexer cells. Different tool versions or optimization settings can change that count while preserving behavior.
Those 689 cells are not 689 transistors. Nor are they a prediction of square micrometers in a particular manufacturing process. The result has not been mapped to a foundry's standard-cell library, placed, routed, or checked against a physical clock target. It is a useful intermediate description with deliberately limited claims.
Account for the state
The register count has a simple explanation. The accumulator uses eighteen bits, the result eighteen, the index two, and busy and done one each. That totals forty. The implementation represents some of those bits with different enable conditions, but the state budget agrees with the design.
This kind of accounting is worth doing before worrying about a sophisticated area estimate. If a tiny change unexpectedly doubles the stored state, investigate. A copied register bank, a widened counter, or an inferred memory may explain the difference.
Likewise, if a multiplier vanishes entirely, check whether its result still reaches an output. Synthesis is allowed to remove logic that cannot affect observable behavior. An empty or very small result may be an optimization success, or it may reveal that your intended circuit was disconnected.
The check command can expose structural problems such as undriven signals or conflicting drivers. It does not understand the application's intended answer. A consistently wired circuit that multiplies by the wrong weight can pass structural checking.
Simulate the translated form
Use the generated netlist in place of the original core while keeping the same testbench and reference vectors:
iverilog -g2012 -s tb_dot4 -o sim-netlist dot4-netlist.v tb_dot4.v
vvp sim-netlist
The edition's generic netlist passed the same 67,541-vector run. That is an additional check that this synthesis path preserved the tested behavior. It is not a formal proof over every possible input sequence.
A formal equivalence check would ask a broader mathematical question about the relationship between the two designs under specified assumptions. Such tools are valuable, but their setup is itself part of the engineering. Reset assumptions, undefined initial state, black boxes, and environmental constraints can determine what was actually proved.
For this first build, keep the claim matched to the run: the generic netlist agrees on the supplied arithmetic vectors and protocol checks. Save the tool version, command, log, and source revision with that statement.
Choose a target before interpreting resources
An FPGA flow may map a multiply to a dedicated arithmetic block if the selected device has a suitable one and the synthesis settings allow it. A standard-cell ASIC flow may build the function from cells in its library. A resource count from one route is not directly interchangeable with a count from the other.
The same applies to memory. An array in the source might infer a device memory primitive under one set of coding rules and become individual registers under another. Read the synthesis report to discover what happened; the source's visual appearance does not settle it.
When comparing two versions, hold the target and tool settings constant. Change one architectural choice and compare the consequences. Otherwise a claimed improvement may come from a different library or mapping option rather than from the new design.
At this stage you should be able to identify the stored state, explain the combinational arithmetic, reproduce the report, and run the translated design. A netlist is less pleasant to read than the original module, but it gives the next tools a concrete circuit to work with.
CHAPTER 10
Bring the circuit to a board
An FPGA is an already manufactured chip whose configurable resources can implement your design. Loading a configuration does not manufacture new transistors. It arranges the use of existing logic, routing, and any hard blocks provided by that device.
That makes it a practical next stage. A mistake can often be corrected by changing the design and loading a new configuration. You can exercise real clocks and pins without waiting for a fabrication run. The result still needs a board-specific implementation, not merely the simulation executable.
Choose a board for which you have the schematic, device documentation, and a supported programming flow. The Lattice iCE40 documentation, for example, collects device and configuration information for that family. It is an example of the documentation you need, not a recommendation that every iCE40 board suits this project.
Begin with the board's known example
Before connecting the accelerator, run the board supplier's smallest documented example. Confirm that you can build, program, and observe it. If an LED does not blink, you want to debug the programming path and pin assignment without simultaneously questioning a signed multiplier.
Record the exact FPGA part and package. A family name alone does not determine pin locations or available resources. Copy constraints from the correct board revision and verify the relevant pins against its schematic.
The clock source also needs an identity. Note its nominal frequency and the pin or internal resource through which it reaches the design. A timing constraint should describe that clock. Declaring a comfortable period in a file does not slow a faster physical oscillator.
For a first accelerator demonstration, use a small synchronous controller inside the FPGA to present known vectors to the byte wrapper. It can compare results internally and expose pass or fail through a simple output. This keeps the initial exercise within one clock domain and avoids making an external communications link the first source of uncertainty.
The controller is additional design work. Its vectors, start pulses, and completion handling need simulation before programming. Reuse the transaction sequence that already passed the wrapper test rather than improvising a different one on the board.
Respect the electrical interface
A logic one is represented by a voltage range determined by the device and I/O standard. It is not permission to attach any wire carrying a voltage somebody calls digital. Check bank supplies, supported standards, input limits, and connector pinouts in the board and device documentation.
Do not assume that a connector's physical compatibility establishes electrical compatibility. Two headers can have the same spacing and different power pins. A board schematic is more useful here than a photograph of a similar setup.
A pushbutton is also not a clean synchronous event. Mechanical contacts can bounce, and the press occurs at an arbitrary time relative to the clock. If you use a button to request a transaction, design the synchronization and debouncing appropriate to that signal. Keep this separate from the multi-bit sample data path.
AMD's multi-bit crossing guidance explains why independently registering bus bits does not automatically make a coherent transfer. A multi-bit value can be assembled from bits belonging to different moments. Use a documented crossing method when separate clock domains become necessary.
Bring up one observation at a time
First verify reset and an idle status value. Then start a transaction and observe busy. Feed zero products with a nonzero bias and read the result. Only after that should you move to negative values, extreme sums, and a sequence of different transactions.
This ordering narrows a failure. If the bias-only case fails, there is little reason to blame multiplication. If positive results work but negative readback fails, inspect sign extension and host decoding. If a second transaction fails after the first succeeds, inspect retained state and completion handling.
Keep the board test separate from the simulator report. Record which bitstream was loaded, how inputs were delivered, and what was observed. A passing internal comparison is stronger evidence when the pass indicator itself has been tested with a deliberately wrong expected value.
The book supplies no board-specific pin file or claimed FPGA measurement. Those depend on the hardware you choose. The portable part is the core, its byte protocol, and its test vectors. Carry those forward as the stable reference while adapting the physical boundary.
CHAPTER 11
Where the numbers spend their time
The multiplication operator is easy to find in the source. The transfers needed to supply its inputs deserve the same attention. In our byte interface, however, it already takes more transfers to deliver a transaction than the core needs active arithmetic cycles.
Count the work before adding parallel units. Four pairs require eight operand bytes. The bias adds two bytes, and the start command adds another write event. The result requires three read selections if the host wants its complete signed value. Status polling adds whatever the host's protocol requires.
A faster multiplier cannot remove those transfers. It may spend most of its time waiting for them. To understand throughput, measure or model the complete route from available input data to a result the consumer can use.
Keep a value where it will be reused
In the synthetic classifier, the same four weights apply to every image. Sending them again for each inference is simple but wasteful. A revised wrapper could load four weight registers once, then stream only four samples per image.
That change adds storage and a weight-address counter. It also introduces questions: when are new weights allowed to replace old ones, does reset erase them, and how does the host know which model is active? Saving transfers creates a new state-management obligation.
Suppose a batch contains one hundred images. Under the present interface, the host sends four hundred weight bytes. A preload design could send four, provided the weights remain unchanged throughout the batch. The arithmetic saving is easy to count. Whether the added storage and control improve the physical implementation requires a target and measurements.
Inputs can be reused too. A four-input layer with several output neurons uses the same input vector with a different weight set for each output. Retaining the vector locally may avoid rereading it from the host for every neuron. Retaining partial sums may avoid exporting and reimporting intermediate results.
These are small examples of dataflow decisions: where operands and intermediate values remain while computation proceeds. The Eyeriss research studies data movement and reuse in a much larger convolutional accelerator. The relevant lesson for our bench is to count movement alongside arithmetic; its measured energy figures should not be transplanted into an unrelated design.
A worked transfer budget
Imagine extending the engine to eight output scores, each based on the same four inputs. Keep the current four-term arithmetic and use a different bias and weight vector for each output.
A straightforward host-driven arrangement sends the four inputs eight times: thirty-two sample bytes. It also sends thirty-two weight bytes and sixteen bias bytes. That is eighty operand-and-bias bytes before counting commands and results.
If the input vector is loaded once and retained, the sample traffic falls from thirty-two bytes to four. If the weights and biases are also retained across many vectors, subsequent inferences can avoid sending them again. The memory required is still small in this example, but the mechanism is the same one that becomes important at larger scale.
Do not count a retained value as free. Registers and memories occupy area, consume power, and need addressing. A memory's port count determines how many values can be accessed together. An array of eight multipliers that each demands two new operands per cycle needs a data organization capable of supplying them.
Follow a stalled transaction
A useful performance trace marks every cycle in which the core is busy but valid is low. Classify why the data was absent. Was the host preparing a sample, sending a weight, waiting for another device, or following a deliberately slow test protocol?
Those categories suggest different changes. More arithmetic parallelism helps none of them automatically. A buffer may absorb bursts. Weight storage may remove repeated transfers. A wider interface may reduce command count. A better host schedule may improve utilization without changing the core at all.
Use completed useful results per unit time as one measure, and state the workload and interface. Peak multiply-accumulate capacity is another measure with a different meaning. Reporting only the peak can make an idle circuit sound busy.
The original TPU analysis by Jouppi and colleagues is worth reading as a study of an actual deployed accelerator and its workloads. Our tiny engine is not a miniature reproduction of that system. It is a place to learn why workload, data supply, and architectural choices belong in the same performance account.
CHAPTER 12
Give the gates somewhere to live
A generic netlist says which logic elements connect. Physical design assigns cells to locations and routes their connections in a particular technology. The wires now have geometry, and geometry affects delay and capacitance.
The manufacturing process is described through a process design kit, or PDK, together with the libraries and flow-specific resources the project requires. The SKY130 documentation is one public example. It includes rules and models used by tools working with that process. It is not a universal adapter for any foundry.
A design hardened for one process cannot simply be relabeled for another. Cell choices, physical dimensions, layer rules, and integration requirements can differ. Start from the supported flow for the actual target instead of combining convenient files from unrelated tutorials.
Read the floorplan as a set of constraints
The floorplan establishes the available region, pins, and major structures. Cells need room not only to exist but to connect. Filling every visible area with logic can make routing difficult or impossible.
For our small core, imagine placing the multiplier far from the accumulator and then asking the connecting path to meet a short clock period. The logical equation has not changed, but the wire contribution has. Physical optimization may move cells, alter drive strengths, or insert buffers to address the problem.
The clock itself must reach many registers. Its distribution needs attention because differences in arrival time affect the relationship between launching and capturing data. Power must also reach the cells through a suitable network. These structures occupy space that the neat arithmetic block diagram did not show.
The OpenROAD documentation describes stages including placement, clock-tree work, routing, extraction, and verification. Use a supported flow to coordinate them. Running one isolated command successfully does not establish that all required stages have completed.
Timing reports need a real question
A setup check asks whether data arrives early enough before the capturing clock edge. A hold check concerns whether it remains stable long enough around that edge. Their detailed analysis uses the clock relationships, cell behavior, interconnect, and chosen operating conditions.
A path that is not constrained may escape the question you thought the tool was answering. Before celebrating positive timing slack, inspect whether the intended clock and external interface requirements were actually included.
For the core, identify input-to-register paths through multiplication and addition, register-to-register feedback through the accumulator, and output paths to the surrounding interface. For a complete wrapper, include the host-facing requirements as well. A timing report for an isolated core cannot certify an arbitrary board connection.
If the target period fails, first understand the critical path. A pipeline register, a different architecture, a slower clock, or a different implementation may help. Changing a constraint merely to make the report green changes the requirement; it does not make the original requirement pass.
Geometry and connectivity are separate checks
Design-rule checking examines whether layout geometry satisfies the applicable rules. Layout-versus-schematic checking compares extracted connectivity with the intended circuit representation. Both address questions different from the functional testbench.
A geometrically legal layout can implement the wrong arithmetic. A functionally correct netlist can have an illegal layout. A clean connectivity comparison does not establish that the clock target is met. Keep the reports together without allowing one to stand in for the others.
The workbench in this edition stops before process-specific hardening. It contains no fabricated-layout claim, tile-fit claim, or measured maximum frequency. That boundary lets you reproduce the demonstrated work without mistaking a generic synthesis count for a manufacturing result.
When you choose a supported physical flow, preserve its exact configuration and logs. The useful deliverable is a reproducible set of source, constraints, technology versions, and checks. A colorful layout image can help you understand where the design went; the reports establish which checks the layout passed.
CHAPTER 13
Below the addition sign
The plus sign in the source is a compact request for hardware. To understand what it can become, begin with three one-bit values: A, B, and an incoming carry. Their sum ranges from zero through three, so two output bits are enough.
The low output bit is one when an odd number of the inputs are one. The carry output is one when at least two inputs are one. This is a full adder. Connecting carry outputs to the next bit's carry inputs gives a ripple-carry arrangement for a wider addition.
You can derive the eight cases yourself by counting the input ones. No decimal arithmetic larger than three is required. The sum bit is the count modulo two; the carry bit is one when the count reaches two. A logic implementation has to preserve those relationships for every input combination.
Follow one carry
Add the four-bit unsigned values seven and one. In binary, they are 0111 and 0001. At the low bit, one plus one produces sum zero and carry one. At the next bit, one plus zero plus the incoming carry again produces zero and a carry. The same happens at the third bit. The final high bit receives the carry and produces one.
The answer is 1000. In a simple ripple structure, the final result depends on a carry moving through several stages. This gives you a concrete reason why the delay of a wider adder cannot be inferred merely by counting the number of characters in a + b.
Other adder structures organize carry computation differently. A synthesis tool and target library may choose a form that differs from your first hand drawing. The mathematical requirement remains the same while area, delay, and wiring change.
Now repeat the exercise for fifteen plus one using only four output bits. The low four bits become zero and an additional carry is needed to represent sixteen. If the interface discards it, the stored result wraps. Nothing in the gate network knows that the application expected a wider answer.
That small example is the unsigned counterpart of the width analysis we performed for the signed accumulator. Bit patterns obey the implemented width. A larger mathematical result does not persuade the register to grow.
Gates become electrical circuits
In static complementary CMOS, transistor networks pull an output toward the appropriate supply level for a logic function. An inverter is the simplest example: its output represents the opposite logical state from its input. More elaborate networks implement functions such as NAND and NOR.
The MIT Computation Structures CMOS materials provide a route from device behavior to gates and timing. That is the next subject to study if you want to understand the transistor-level implementation, rather than treating a library cell as a named box.
A digital abstraction groups ranges of voltage into logical states. The physical circuit still has capacitance, finite drive, and transition time. A gate driving a heavier load may respond differently from the same gate driving a light one. This is why cell characterization and interconnect matter in timing analysis.
Our RTL simulator hides most of that physical detail on purpose. It allows us to test logical sequencing efficiently. A transistor-level simulation asks different questions at greater cost and with technology-specific models. Choosing the level of detail appropriate to the question is part of the work.
Build a small adder as a side project
Write a one-bit full adder with Boolean operations, then connect four instances. Give it a five-bit output so that every sum of two unsigned four-bit inputs can be represented. Generate all 256 operand pairs and compare them with a direct software addition.
This is a separate exercise from the signed inference core. Keeping it unsigned initially makes carry behavior easier to inspect. Once it passes, decide explicitly how a signed version should extend inputs and interpret outputs. Reuse the habit of writing the range argument before the code.
Then compare the hand-connected implementation with a simple addition operator under the same synthesis target. They may map similarly, or the tool may transform them. Inspect the result before assuming that handwritten gate structure must be smaller or faster.
You do not have to hand-design every transistor to own the architecture of a chip. You do need to understand enough of the layers below your description to recognize when a promise about area, timing, or power lacks the evidence it requires. The four-bit adder gives that understanding a manageable first circuit.
CHAPTER 14
A place on a wafer
A multi-project fabrication run combines designs from several participants. Sharing a run makes small educational designs more approachable than ordering an entire custom chip program alone. The available process, area, interfaces, schedule, and delivery format belong to the particular service and run.
Tiny Tapeout is one route to investigate. Its documentation distinguishes shuttle generations and technologies, and directs readers to current schedules and pricing. Treat those as live project information. A remembered tile size or an old delivery estimate is a poor basis for committing a new design.
The workbench has not been submitted. There is no fabrication order associated with this book, and no claim that the core plus wrapper fits a particular tile. Before making such a claim, harden the complete integrated design with the intended run's flow and inspect its reports.
Start with the right template
Tiny Tapeout's HDL template page lists separate starting points for different technologies. Choose the one associated with the intended shuttle. Read its top-level interface, documentation requirements, and test setup before adapting the core.
A top-level wrapper connects the project's internal signals to the service's expected ports. The standard template includes dedicated input and output buses, bidirectional paths and enables, clock, reset, and an enable input. Unused outputs need defined values. Bidirectional direction controls deserve the same review as the data signals.
Our byte interface has a plausible shape for a small pin budget, but plausibility is not integration. Assign each signal explicitly. Check reset polarity and behavior. Ensure that the surrounding host presents address and data coherently. Simulate the actual top-level wrapper, including its pin mapping, rather than only the core behind it.
Keep the original notices of any template or library you use. The workbench's own code has an MIT license, but that does not replace the licenses attached to other components. A reproducible project includes the sources and conditions under which they can be used.
Freeze an artifact, not a folder name
The submission guide describes selecting the project and the artifact to submit. Follow the current instructions for the run. A source repository that contains a recent fix does not necessarily mean an earlier submission has been replaced by that fix.
Record a source commit, the generated artifact, tool and technology versions, and the checks that passed. Give the submission record the same identifiers. The goal is to answer a simple future question: exactly which design went into this run?
Avoid editing an already reviewed source tree while generating the final artifact. If a change becomes necessary, rerun the relevant checks and make a new record. A one-character change to a reset condition can be more consequential than a large documentation edit.
The first project should leave room in both area and schedule for corrections. Do not make a deadline depend on an untested optimization discovered the night before. A smaller circuit with an understood interface is easier to bring up than a crowded design whose behavior was still changing at submission.
Plan the first conversation with the chip
Before hardware arrives, write the host procedure. It should begin with the board or breakout's documented power and connection requirements, then reset, a known status read, a simple arithmetic case, and negative readback. Keep the expected bytes beside each step.
Use the same reference vectors wherever the physical interface permits. Record discrepancies as actual inputs, outputs, clock settings, and conditions. “It behaves strangely” is the beginning of an observation, not enough information to compare with simulation.
A manufactured result can fail because of design logic, physical implementation, assembly, power, wiring, or host software. The earlier artifacts help isolate those possibilities. If the same wrong byte appears in a simulation of the top-level wrapper, there is little reason to begin by blaming fabrication.
The package you receive also matters. A bare die, a packaged integrated circuit, and an assembled breakout board are different things to handle and connect. Read the delivery description for the selected service. Do not design a host board around a format you merely assumed would arrive.
The first success can be modest: reset works, a command is accepted, and the bytes decode to one. Keep the measurement. Record the setup, command and returned bytes before extending the test.
CHAPTER 15
The second version needs a reason
After the first working design, every improvement looks close enough to add. More terms need only a wider counter. More outputs suggest another accumulator. A faster clock seems to ask for one pipeline register. The small changes soon interact.
Choose a measured problem for the second version. Perhaps the host spends most of its time retransmitting weights. Perhaps the combinational path prevents the desired clock period. Perhaps the application needs eight output scores from the same vector. Write that problem before choosing the new architecture.
Compare two proposed changes
Consider a weight-preload version and a four-multiplier version. The first keeps one multiplier but stores weights. The second tries to calculate all four products concurrently. They address different limits.
If the host remains restricted to one eight-bit write at a time, the parallel version still needs its operands to arrive. Unless it stores them or receives a wider transfer, it cannot keep four multipliers usefully occupied. The preload version may improve the actual transaction rate with less arithmetic hardware because it removes repeated transfers.
If inputs and weights are already available together inside a larger system, parallel multiplication may be more attractive. It will also need a way to combine the products, and timing through that addition network must be checked. The environment changes which design makes sense.
Write a cycle schedule for both candidates. Include loading, start, computation, result availability, and any interval before another transaction can begin. Do not report only the active multiply cycles. The host experiences the complete schedule.
Change the reference before the implementation
Suppose the new requirement is a sixteen-term dot product. Extend the reference and calculate the new bounds first. The old eighteen-bit result range may no longer cover every allowed bias and operand combination. Update the interface contract and directed extreme vectors before widening the hardware.
If the requirement adds saturation, define exactly where it happens. Saturating after every partial addition can differ from accumulating in a wide register and saturating only at the end, because later negative terms can bring an earlier large positive sum back into range. The choice affects the answer, not only the implementation cost.
For an original numerical example, imagine a limit of ten and the sequence plus eight, plus eight, minus eight. Saturating each partial sum yields eight, ten, then two. Adding exactly and clipping only the final result yields eight. Both rules are possible; they are not interchangeable.
If you add rounding, test values on both sides of a halfway case and include negative values. If you add a nonlinear activation, specify its domain and output format. If you add a queue, define what happens when it fills. Each new feature should arrive with an observable rule and a test that can reject a wrong implementation.
Keep a short measurement notebook
Use one row per source revision. Record the workload, interface, tool versions, target, resource report, timing result where available, and tests. Separate predictions from board measurements. A simulation cycle count can be exact while the clock frequency used to turn it into seconds remains hypothetical.
For example, five clock intervals between accepted starts would imply one result every five periods under a particular schedule. At an assumed ten-megahertz clock, that is half a microsecond. The arithmetic conversion is correct; it is not evidence that the physical design meets that clock or that the host can sustain the schedule.
When measuring a board, include host overhead if the claim concerns application latency. When measuring only the core, label that boundary. Both measurements can be useful, but comparing one design's core time with another system's end-to-end time produces a misleading ranking.
The same care applies to power. A board's total draw includes components beyond your logic. A tool estimate depends on switching assumptions and technology data. Do not attach a measured unit to a guessed number simply because the result appears in a table.
Leave an experiment someone can continue
The workbench includes the exact reference, core, byte wrapper, testbenches, vector generators, and synthetic training example. Its tests are intended to be rerun after changes. Generated vector files and simulation executables can be recreated; the source and the reason for each check are the durable part.
Try weight preloading first. Keep the original core unchanged, add four weight registers to a new wrapper, and define a loading command. Simulate an update between transactions, an attempted update during a transaction, reset after loading, and two different input vectors using the same weights.
Then count the transfers for one image and for one hundred. Synthesize both wrappers with the same settings. The comparison shows the transfer savings and additional logic. Put those numbers beside the old version before deciding whether to keep the change.
Sources & edition note
Revised September 5: tightened chapter openings, transitions and closing instructions for more direct prose. Technical examples, numerical results and source qualifications are retained.
This is an AI-generated educational guide. Ada Vale is a fictional editorial pen name. The workbench is original code and was executed during preparation: 67,541 arithmetic vectors passed through the RTL core, the byte wrapper, and a generic synthesized netlist. All 256 cases of the synthetic classifier matched the arithmetic reference. The source package includes the test procedures, versions, and limits.
No FPGA board was programmed, no process-specific layout was hardened, and no silicon was fabricated for this edition. The book makes no measured frequency, power, tile-fit, or physical-area claim. Its synthetic classifier is a teaching exercise, not a real vision benchmark. Sources were consulted on 4 September 2026; fabrication services and tool interfaces should be checked again for an actual project.
- Icarus Verilog · Getting started ↗
Simulator compilation and execution. Edition tests used Icarus Verilog 12.0.
- Yosys · Synthesis starter ↗
Official synthesis documentation. The book’s original core was tested with Yosys 0.33 using generic synthesis without ABC.
- Jacob et al. · Integer-arithmetic-only inference ↗
Primary research on quantization and training for integer inference. The workbench implements a simpler primitive, not the full paper.
- Chen, Emer and Sze · Eyeriss ↗
ISCA 2016. Primary research on dataflow and data movement; its measurements are not claimed for the workbench.
- Jouppi et al. · In-datacenter TPU analysis ↗
2017 analysis of a deployed accelerator and its workloads. Background reading, not a benchmark comparison with this project.
- AMD · Multi-bit synchronizer guidance ↗
Official explanation of the limits of independently synchronized bus bits. The supplied wrapper assumes synchronous inputs.
- Lattice · iCE40 documentation ↗
An example device-family documentation collection. A real board needs its own exact schematic, part, package and electrical constraints.
- OpenROAD · Physical design flow ↗
Placement, routing, clock distribution and physical verification stages.
- SkyWater · SKY130 PDK contents ↗
Example public process documentation. No SKY130 hardening was performed for the book.
- MIT OpenCourseWare · CMOS ↗
Further study of transistor behavior, CMOS gates and timing. Original worked arithmetic in this book is not copied course material.
- Tiny Tapeout · FAQ ↗
Run-specific fabrication, interface and delivery information. Check the current run rather than generalizing older specifications.
- Tiny Tapeout · HDL templates ↗
Technology-specific templates for an actual integration project.
- Tiny Tapeout · SKY top-level example ↗
Reference port names and direction controls; the workbench does not include a submitted Tiny Tapeout integration.
- Tiny Tapeout · Submission guide ↗
Selecting and updating the exact design revision submitted to a run.