Project structure

Organizing Your Code

Project layout, namespaces, component boundaries, naming, and API style for maintainable Livt systems.

Small Livt examples fit in one file. Real projects do not. As soon as a design has multiple protocols, test components, reusable helpers, and top-level integration code, organization becomes part of the design.

Good organization makes hardware easier to review. A reader should be able to find the top level, identify component boundaries, understand which modules are reusable, and see where tests live.

Project Layout

A typical Livt project separates source code from tests:

text
MyProject/
|-- livt.toml
|-- src/
|   |-- App.lvt
|   |-- PacketParser.lvt
|   `-- RegisterBank.lvt
`-- tests/
    |-- PacketParserTest.lvt
    `-- RegisterBankTest.lvt

Use src/ for synthesizable design code and shared design helpers. Use tests/ for test components, simulation scenarios, and assertions.

Keeping this separation clear helps the build and test workflow stay predictable. It also makes code review easier: a test file can use simulation-only APIs, while a source file should be more careful about synthesis boundaries.

Namespaces at Scale

Namespaces should communicate ownership and purpose:

livt
namespace Livt.App.Net
namespace Livt.App.Registers
namespace Livt.App.Tests

Avoid putting all components into one large namespace. Instead, group code by domain: networking, register access, memory, protocol helpers, tests, simulation fixtures, and top-level integration.

Imports should stay boring:

livt
namespace Livt.App.Tests

using Livt.App.Net
using Livt.App.Registers

If a file needs many unrelated namespaces, that is often a sign that the component is doing too much.

Component Boundaries

A component should have one clear responsibility. Good component names usually describe a role:

  • PacketParser
  • ChecksumVerifier
  • RegisterBank
  • UartTransmitter
  • AxiLiteSlave
  • HttpRequestRecognizer

Avoid components that only wrap a primitive operation. Livt lets you write high-level expressions directly; you do not need a component for every operator.

A useful component boundary often appears where one of these is true:

  • The code owns state.
  • The code exposes a public API.
  • The code connects to a protocol boundary.
  • The code deserves independent tests.
  • The code may be reused in another design.

Public API Style

The public surface of a component is the part other code will depend on. Keep it small and intentional:

livt
component PacketStatistics
{
    public acceptedCount: int
    public droppedCount: int

    public fn Accept()
    {
        this.acceptedCount = this.acceptedCount + 1
    }

    public fn Drop()
    {
        this.droppedCount = this.droppedCount + 1
    }
}

Here the counters are public because they are status values. The update behavior is exposed through named functions. That is easier to read than allowing callers to modify several implementation fields directly.

Use private fields for implementation details. Use public stored fields for observable state. Use public signal fields for hardware ports and directed signals.

File Size and Reviewability

A file should usually contain one primary component or interface. Small helper interfaces or constants can live nearby, but large files become difficult to review.

Prefer several focused files over one broad file:

text
src/
|-- IByteStream.lvt
|-- ByteStreamParser.lvt
|-- PacketStatistics.lvt
`-- PacketPipeline.lvt

This makes generated VHDL easier to map back to source and helps tests target one behavior at a time.

Naming

Use names that describe intent, not implementation accidents.

Good names:

  • IsAsciiDigit
  • TryRead
  • HasData
  • AcceptPacket
  • payloadLength
  • rxFrame

Weak names:

  • DoIt
  • Handle
  • tmp2
  • flag
  • data1

Short names are fine for local loop counters. Public fields, functions, interfaces, and components should be descriptive.

Top-Level Code

The top-level component is the boundary to the outside world. Keep it direct:

  • expose physical signals clearly
  • instantiate major subcomponents
  • wire interfaces and buses
  • avoid burying protocol behavior in the top level

The top level should read like a system map. Detailed behavior belongs in the components it connects.

Summary

Organized Livt code has clear namespaces, focused files, meaningful components, small public APIs, and tests close to the behavior they verify. The structure of the code should help the reader understand the hardware architecture before they read the details.