Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ jobs:
run: cabal run gatework -- --netlist fixtures/counter.net --duration 8 --output /tmp/counter.vcd
- name: Compare the counter demo with the golden file
run: diff fixtures/counter.golden.vcd /tmp/counter.vcd
- name: Run the waveform report demo
run: cabal run gatework -- report --netlist fixtures/counter.net --duration 8 --output /tmp/counter.report
- name: Compare the report demo with the golden file
run: diff fixtures/counter.golden.report /tmp/counter.report
- name: Run the register demo
run: cabal run gatework -- --netlist fixtures/register.net --duration 8 --output /tmp/register.vcd --set d=1 --at 2 d=0 --at 4 d=1 --at 6 d=0
- name: Compare the register demo with the golden file
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ All notable changes to Gatework appear in this file.
The format follows the Keep a Changelog convention.
This project uses semantic versioning.

## [0.10.0.0] - 2026-08-03

### Added

- Add the report command to print the signal values as a text table.
- Each row shows one change time and every signal value.
- Print the report to standard output without the --output option.
- Write the report to a file with the --output option.
- Add a counter report golden file.
- Add deterministic tests and a QuickCheck property for the report.
- Extend the CI workflow to run the report demo.

### Changed

- Bump the package version to 0.10.0.0.
- Update the golden VCD files to the new version string.

## [0.9.0.0] - 2026-08-03

### Added
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ The CI workflow runs these checks.
- `cabal test --enable-tests` runs the test suite.
- Every demo writes a VCD file.
- Each demo output must match its golden file.
- The report demo writes a text table.
- The report output must match its golden file.

Run the checks before you open a pull request.

Expand Down
57 changes: 52 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

Gatework is an event-driven digital logic simulator in Haskell.
It parses a plain-text netlist, runs the circuit, and writes a VCD waveform file.
A report command prints the signal values as a text table.
GTKWave and other waveform viewers can open the output.
The simulator uses four logic values: low, high, unknown, and floating.
Multi-bit buses use bracketed widths and slices.
Expand All @@ -25,6 +26,7 @@ You can do these tasks:
- Load a module library with the `--library` option.
- Build a hierarchical adder from a module library file.
- Open the VCD output in GTKWave and inspect the waveforms.
- Print a text waveform report with the report command.
- Verify gate behavior with QuickCheck property tests.
- Compare the counter waveform with a repository golden file.
- Check netlist behavior with waveform assertions.
Expand All @@ -45,20 +47,22 @@ You can do these tasks:

## Architecture

The project has four library modules.
The project has five library modules.

| Module | Responsibility |
| --- | --- |
| `Gatework.Logic` | Defines logic values and gate functions |
| `Gatework.Netlist` | Parses, validates, and flattens circuit files |
| `Gatework.Report` | Renders the waveform as a text table |
| `Gatework.Simulator` | Schedules signal changes |
| `Gatework.VCD` | Renders the waveform text |

The data flow is:

```text
library text -> parser -> library module table
netlist text -> parser -> module table -> flattened netlist -> event queue -> waveform recorder -> assertion check -> VCD
netlist text -> parser -> module table -> flattened netlist -> event queue -> waveform recorder -> VCD
waveform recorder -> text table (report command)
```

The parser validates names, gate arity, drivers, clocks, and references.
Expand All @@ -76,6 +80,7 @@ An asserted reset forces flip-flop outputs to their initial values.
The recorder keeps the initial value and every later transition.
The assertion checker compares declared expectations with the waveform.
The VCD writer uses stable signal order and stable identifiers.
The report writer prints one row per change time.

The repository layout is:

Expand Down Expand Up @@ -158,6 +163,37 @@ Its value sequence is 0, 1, 2, 3, and 4.
| 5 | 0 | 0 | 1 | 1 | 3 |
| 7 | 0 | 1 | 0 | 0 | 4 |

## Waveform report

Print the counter as a text table.

```powershell
cabal run gatework -- report --netlist fixtures/counter.net --duration 8
```

The command prints this table:

```text
time | q0 | q1 | q2 | q3 | clk | d0 | d1 | carry2 | d2 | carry3 | d3
---- | -- | -- | -- | -- | --- | -- | -- | ------ | -- | ------ | --
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0
1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0
2 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0
3 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0
4 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0
5 | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0
6 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0
7 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0
8 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0
```

The table shows one row per change time.
Each column shows one signal.
Each cell shows the settled value at that time.
Use `--output FILE` to write the table to a file.
Without `--output`, the table prints to standard output.
The report uses the same options as the VCD command.

## Ripple-carry adder

Run three plus five with explicit input values.
Expand Down Expand Up @@ -491,6 +527,9 @@ This excerpt shows the first timestamp:
Each identifier is one signal.
The header maps identifiers to signal names.

The file `fixtures/counter.golden.report` holds the counter report table.
It shows one row per change time.

The file `fixtures/register.golden.vcd` holds the complete register waveform.
This excerpt shows the first two timestamps:

Expand Down Expand Up @@ -845,12 +884,14 @@ Deterministic tests also cover undefined and floating values, tri-state buffers,
Deterministic tests also cover bus declarations, bitwise gates, bit references, slices, bus registers, bus assertions, bus module ports, and error cases.
Deterministic tests also cover multi-driver resolution, scheduled driver changes, flip-flop output exclusivity, the shared-bus fixture, and its golden output.
Deterministic tests also cover module library loading, cross-library module references, duplicate module names, and library file validation.
Deterministic tests also cover the report command, its header order, its counter table, and its golden output.
QuickCheck properties cover gate algebra, full adder correctness, scheduled input sampling, reset sampling, register width, and assertion soundness.
QuickCheck properties also compare the hierarchical adder and counter with their flat versions.
QuickCheck properties also cover the four-state model and the tri-state buffer truth table.
QuickCheck properties also compare a bus circuit with a bitwise reference model.
QuickCheck properties also compare the shared bus with a per-time resolution model.
QuickCheck properties also compare a library adder with its flat version.
QuickCheck properties also compare the counter report with the simulated waveform.

QuickCheck runs one hundred random cases for each property.
The gate properties cover the complete truth table.
Expand All @@ -868,13 +909,16 @@ The bus register property compares each register bit with a reference value.
The bus hierarchy property shows that a bus module matches its flat circuit.
The shared-bus property compares each resolved wire value with a per-time model.
The library adder property compares a library netlist with the flat adder.
The counter report property compares each report cell with the simulated value.

## Test status

All tests pass on GHC 9.6.7 with Cabal 3.14 in the bundled container.
The CI workflow runs the same checks on Ubuntu with GHC 9.6.6.
Golden tests compare the counter, register, reset, two-bit register, assertion, gate, hierarchical adder, library adder, hierarchical counter, adder, tri-state, undefined-state, bus, and shared-bus VCD text.
The golden report test compares the counter report table with its golden file.
CI runs every demo and compares its output with the golden file.
CI runs the report demo and compares it with the report golden file.
CI confirms that a missing library file stops the run.

## Limitations
Expand Down Expand Up @@ -913,9 +957,13 @@ The CLI does not accept whole-bus values.
One module declaration cannot live inside another.
An instance output must connect to a declared signal.
The dotted instance names are part of the VCD signal names.
The report prints one row per change time.
The report uses the settled value at each time.
The report prints every signal in the stable signal order.

## Roadmap

Release 0.10.0.0 completed the waveform report command.
Release 0.9.0.0 completed module libraries.
Release 0.8.0.0 completed multi-driver wire resolution.
Release 0.7.0.0 completed multi-bit buses, bit references, slices, and bus module ports.
Expand All @@ -927,9 +975,8 @@ Release 0.2.0.0 completed scheduled input transitions.

Remaining work:

1. Add a waveform report command.
2. Add whole-bus input values on the command line.
3. Add multi-bit values in the VCD timeline.
1. Add whole-bus input values on the command line.
2. Add multi-bit values in the VCD timeline.

## License

Expand Down
89 changes: 60 additions & 29 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module Main (main) where
import Data.List (intercalate)
import Gatework.Logic (Logic, logicChar, parseLogic)
import Gatework.Netlist (netlistSignals, parseNetlistFileWithLibraries)
import Gatework.Report (renderReport)
import Gatework.Simulator
( AssertionFailure (..)
, Simulation
Expand All @@ -14,52 +15,82 @@ import Gatework.Simulator
import Gatework.VCD (writeVCD)
import System.Environment (getArgs)
import System.Exit (exitFailure)
import System.IO (hPutStrLn, stderr)
import System.IO (Handle, hPutStrLn, stderr, stdout)
import Text.Read (readMaybe)

data Options = Options
{ optionNetlist :: Maybe FilePath
, optionDuration :: Integer
, optionOutput :: FilePath
, optionOutputExplicit :: Bool
, optionInputs :: [(String, Logic)]
, optionScheduled :: [(Time, String, Logic)]
, optionLibraries :: [FilePath]
}

data Command = CommandRun Options | CommandReport Options

defaultOptions :: Options
defaultOptions = Options Nothing 16 "gatework.vcd" [] [] []
defaultOptions = Options Nothing 16 "gatework.vcd" False [] [] []

main :: IO ()
main = do
arguments <- getArgs
case parseOptions arguments of
case parseCommand arguments of
Left message -> failWith message
Right Nothing -> putStrLn usage
Right (Just options) -> run options

run :: Options -> IO ()
run options = case optionNetlist options of
Nothing -> failWith "--netlist is required"
Just path -> do
parsed <- parseNetlistFileWithLibraries (optionLibraries options) path
case parsed of
Left message -> failWith message
Right netlist ->
case simulateWithScheduledInputs netlist (optionInputs options) (optionScheduled options) (optionDuration options) of
Left message -> failWith message
Right simulation -> do
writeVCD (optionOutput options) simulation
putStrLn ("Wrote " ++ optionOutput options)
putStrLn ("Signals: " ++ show (length (netlistSignals netlist)))
putStrLn ("Duration: " ++ show (optionDuration options) ++ " time units")
reportAssertions simulation

reportAssertions :: Simulation -> IO ()
reportAssertions simulation = case simulationFailures simulation of
Right (Just command) -> run command

parseCommand :: [String] -> Either String (Maybe Command)
parseCommand ("report" : rest) = fmap CommandReport <$> parseOptions rest
parseCommand arguments = fmap CommandRun <$> parseOptions arguments

run :: Command -> IO ()
run command = do
let options = commandOptions command
case optionNetlist options of
Nothing -> failWith "--netlist is required"
Just path -> do
parsed <- parseNetlistFileWithLibraries (optionLibraries options) path
case parsed of
Left message -> failWith message
Right netlist ->
case simulateWithScheduledInputs netlist (optionInputs options) (optionScheduled options) (optionDuration options) of
Left message -> failWith message
Right simulation -> do
wroteFile <- emit command simulation
let summaryHandle = if wroteFile then stdout else stderr
if wroteFile
then putStrLn ("Wrote " ++ optionOutput options)
else pure ()
hPutStrLn summaryHandle ("Signals: " ++ show (length (netlistSignals netlist)))
hPutStrLn summaryHandle ("Duration: " ++ show (optionDuration options) ++ " time units")
reportAssertions summaryHandle simulation

commandOptions :: Command -> Options
commandOptions (CommandRun options) = options
commandOptions (CommandReport options) = options

emit :: Command -> Simulation -> IO Bool
emit (CommandRun options) simulation = do
writeVCD (optionOutput options) simulation
pure True
emit (CommandReport options) simulation =
let text = renderReport simulation
in if optionOutputExplicit options
then do
writeFile (optionOutput options) text
pure True
else do
putStr text
pure False

reportAssertions :: Handle -> Simulation -> IO ()
reportAssertions summaryHandle simulation = case simulationFailures simulation of
[] -> case simulationAssertions simulation of
[] -> pure ()
assertions ->
putStrLn ("Assertions: " ++ show (length assertions) ++ " passed")
hPutStrLn summaryHandle ("Assertions: " ++ show (length assertions) ++ " passed")
failures -> do
mapM_ (hPutStrLn stderr . renderFailure) failures
exitFailure
Expand All @@ -86,7 +117,7 @@ parseOptions arguments = parseMore defaultOptions arguments
"--duration" : value : rest -> do
duration <- maybe (Left "--duration requires an integer") Right (readMaybe value)
parseMore options {optionDuration = duration} rest
"--output" : path : rest -> parseMore options {optionOutput = path} rest
"--output" : path : rest -> parseMore options {optionOutput = path, optionOutputExplicit = True} rest
"--library" : path : rest ->
parseMore options {optionLibraries = optionLibraries options ++ [path]} rest
"--set" : assignments : rest -> do
Expand Down Expand Up @@ -120,14 +151,14 @@ failWith message = do

usage :: String
usage = intercalate "\n"
[ "gatework --netlist FILE [--library FILE] [--duration N] [--output FILE] [--set signal=0,signal=1,signal=x,signal=z] [--at TIME signal=0,signal=1,signal=x,signal=z]"
[ "gatework [report] --netlist FILE [--library FILE] [--duration N] [--output FILE] [--set signal=0,signal=1,signal=x,signal=z] [--at TIME signal=0,signal=1,signal=x,signal=z]"
, ""
, "Simulate a netlist and write a VCD waveform."
, "Run 'gatework report' to write a text waveform table instead."
, "Without --output, the report prints to standard output."
, "Use --library to load reusable module definitions from another file."
, "Repeat --library to load more than one module file."
, "Use --at to change input signals at a fixed time during the run."
, "Use x for an unknown value and z for a floating value."
, "Check assert declarations in the netlist against the waveform."
]


2 changes: 1 addition & 1 deletion fixtures/adder.golden.vcd
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ $date
generated deterministically by gatework
$end
$version
gatework 0.9.0.0
gatework 0.10.0.0
$end
$timescale 1ns $end
$scope module gatework $end
Expand Down
2 changes: 1 addition & 1 deletion fixtures/assert.golden.vcd
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ $date
generated deterministically by gatework
$end
$version
gatework 0.9.0.0
gatework 0.10.0.0
$end
$comment
assert y = 1 at 0
Expand Down
2 changes: 1 addition & 1 deletion fixtures/bus.golden.vcd
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ $date
generated deterministically by gatework
$end
$version
gatework 0.9.0.0
gatework 0.10.0.0
$end
$comment
assert x[0] = 1 at 0
Expand Down
11 changes: 11 additions & 0 deletions fixtures/counter.golden.report
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
time | q0 | q1 | q2 | q3 | clk | d0 | d1 | carry2 | d2 | carry3 | d3
---- | -- | -- | -- | -- | --- | -- | -- | ------ | -- | ------ | --
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0
1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0
2 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0
3 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0
4 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0
5 | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0
6 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0
7 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0
8 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0
2 changes: 1 addition & 1 deletion fixtures/counter.golden.vcd
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ $date
generated deterministically by gatework
$end
$version
gatework 0.9.0.0
gatework 0.10.0.0
$end
$timescale 1ns $end
$scope module gatework $end
Expand Down
2 changes: 1 addition & 1 deletion fixtures/gates.golden.vcd
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ $date
generated deterministically by gatework
$end
$version
gatework 0.9.0.0
gatework 0.10.0.0
$end
$comment
assert nand_out = 1 at 0
Expand Down
Loading
Loading