A function is a named routine that takes inputs, computes, and hands back a value, all in zero simulated time. Where the previous page's tasks were built to live in time, driving, waiting, holding, a function is built to be instant: call it, and the answer exists in the same moment, no delay, no clock, no waiting. That instancy is not a limitation, it is the contract. When you call a function you are asking a question, not starting an activity, and the language holds you to it.
The contract is enforced. A delay inside a function is illegal, a wait is illegal, and calling a time consuming task from inside a function is illegal, because any of them would make the instant routine take time, and the compiler refuses outright. This is not a style rule that lint politely flags; it is a hard error, and in this lab you can see it for yourself: put a delay inside your function, and the engine will stop you at compile with the exact line. The boundary is worth memorising as a sentence: if the job involves time, it is a task, the routine that lives in time; if the job is pure computation, it is a function.
The best functions read only their arguments and touch nothing outside themselves. Same inputs, same answer, every single call, no matter what the rest of the testbench is doing. That property is called purity, and it is why functions become the reference brain of a testbench: when you need to know what the design should have produced, you ask a pure function, precisely because its answer cannot be contaminated by anything else. Hold that thought, because the primer's final page builds a self checking testbench, and the checker at its heart is exactly this kind of function. One habit to adopt now: declare your functions automatic, the same one word you learned on the tasks page, so every call carries its own private storage and the function stays independent no matter how it is used.
Here is the trap that produces wrong answers with no error at all. A function's return type is part of its declaration, and if you leave it out, the language does not object, it quietly defaults the return to a single bit. Your function then computes a perfectly correct wide answer and hands back one bit of it. Count eight ones in a byte, and a one bit return delivers zero, because eight in binary is a one followed by three zeros and only the lowest bit survives. You met this class of bug on the vectors page, where a value that does not fit is silently truncated: the value simply wrong from that point on. The rule is the same as it was there: count the bits your answer needs, and declare exactly that many on the return. This lab's checker calls your function with values chosen to expose a forgotten or narrow return the moment it happens.
Write a function named count_ones that takes an eight bit value and returns how many of its bits are one. The answer can be as large as eight, so choose a return type wide enough to hold it. Use a loop to walk the bits, the forms from the loops page all work. The check section below your function is already written and must not be modified: it calls count_ones on five known bytes and prints each result. Run and submit. A missing or narrow return type will corrupt specific lines, and the engine will name them; a delay inside the function will not even compile.
Weekly intelligence on VLSI, AI for EDA, and chip careers. 20,000+ engineers already inside.