Skip to content

Commit

Permalink
Merge pull request #57 from leonardt/internal-signals
Browse files Browse the repository at this point in the history
Add primitive support for internal signals
  • Loading branch information
leonardt committed Feb 5, 2019
2 parents 73ecb0f + 14b4966 commit 109ddce
Show file tree
Hide file tree
Showing 29 changed files with 1,429 additions and 172 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Added preliminary support for peek and expect on internal signals and poke on
internal registers

[Unreleased]: https://github.com/leonardt/fault/compare/v1.0.0...HEAD
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,81 @@
[![Coverage Status](https://coveralls.io/repos/github/leonardt/fault/badge.svg?branch=master)](https://coveralls.io/github/leonardt/fault?branch=master)

A Python package for testing hardware (part of the magma ecosystem).

## Example
Here is a simple ALU defined in magma.
```python
import magma as m
import mantle


class ConfigReg(m.Circuit):
IO = ["D", m.In(m.Bits(2)), "Q", m.Out(m.Bits(2))] + \
m.ClockInterface(has_ce=True)

@classmethod
def definition(io):
reg = mantle.Register(2, has_ce=True, name="conf_reg")
io.Q <= reg(io.D, CE=io.CE)


class SimpleALU(m.Circuit):
IO = ["a", m.In(m.UInt(16)),
"b", m.In(m.UInt(16)),
"c", m.Out(m.UInt(16)),
"config_data", m.In(m.Bits(2)),
"config_en", m.In(m.Enable),
] + m.ClockInterface()

@classmethod
def definition(io):
opcode = ConfigReg(name="config_reg")(io.config_data, CE=io.config_en)
io.c <= mantle.mux(
[io.a + io.b, io.a - io.b, io.a * io.b, io.a / io.b], opcode)
```

Here's an example test in fault that uses the configuration interface, expects
a value on the internal register, and checks the result of performing the
expected operation.

```python
import operator

ops = [operator.add, operator.sub, operator.mul, operator.div]
tester = fault.Tester(SimpleALU, SimpleALU.CLK)
tester.circuit.CLK = 0
tester.circuit.config_en = 1
for i in range(0, 4):
tester.circuit.config_data = i
tester.step(2)
tester.circuit.a = 3
tester.circuit.b = 2
tester.eval()
tester.circuit.c.expect(ops[i](3, 2))
```

We can run this with three different simulators

```python
tester.compile_and_run("verilator", flags=["-Wno-fatal"],
magma_opts={"verilator_debug": True}, directory="build")
tester.compile_and_run("system-verilog", simulator="ncsim", directory="build")
tester.compile_and_run("system-verilog", simulator="vcs", directory="build")
```

If you're using `mantle.Register` from the `coreir` implementation, you can
also poke the internal register value directly using the `value` field. Notice
that `conf_reg` is defined in `ConfigReg` to be an instance of
`mantle.Register` and the test bench pokes it by setting `confg_reg.value`
equal to `1`.

```python
tester = fault.Tester(SimpleALU, SimpleALU.CLK)
tester.circuit.CLK = 0
# Initialize
tester.step(2)
for i in reversed(range(4)):
tester.circuit.config_reg.conf_reg.value = i
tester.step(2)
tester.circuit.config_reg.conf_reg.O.expect(i)
```
98 changes: 98 additions & 0 deletions doc/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,101 @@ for i in range(n):
`Print(port, format_str)` prints the value read at `port` (`port` can be an input or an output). Similar to Expect, the values read by output ports at the time of Print are the same as those at the time of the last Eval.

`format_str` allows for user-specified formatting of the printed output (similar to `printf` format strings).

## WrappedVerilogInternalPort
Fault has primitive support for working with internal verilog signals using the
`verilator` target.

Suppose you had a file `simple_alu.v`. Notice that the desired internal
signals, `ConfigReg.Q` and `SimpleALU.opcode` are marked with a comment
`/*verilator public*/`.

```verilog
// simple_alu.v
module ConfigReg(input [1:0] D, output [1:0] Q, input CLK, input EN);
reg Q/*verilator public*/;
always @(posedge CLK) begin
if (EN) Q <= D;
end
endmodule
module SimpleALU(input [15:0] a, input [15:0] b, output [15:0] c, input [1:0] config_data, input config_en, input CLK);
wire [1:0] opcode/*verilator public*/;
ConfigReg config_reg(config_data, opcode, CLK, config_en);
always @(*) begin
case (opcode)
0: c = a + b;
1: c = a - b;
2: c = a * b;
3: c = a / b;
endcase
end
endmodule
```

We can wrap the verilog using magma

```python
SimpleALU = m.DefineFromVerilogFile("tests/simple_alu.v",
type_map={"CLK": m.In(m.Clock)},
target_modules=["SimpleALU"])[0]

circ = m.DefineCircuit("top",
"a", m.In(m.Bits(16)),
"b", m.In(m.Bits(16)),
"c", m.Out(m.Bits(16)),
"config_data", m.In(m.Bits(2)),
"config_en", m.In(m.Bit),
"CLK", m.In(m.Clock))
simple_alu = SimpleALU()
m.wire(simple_alu.a, circ.a)
m.wire(simple_alu.b, circ.b)
m.wire(simple_alu.c, circ.c)
m.wire(simple_alu.config_data, circ.config_data)
m.wire(simple_alu.config_en, circ.config_en)
m.wire(simple_alu.CLK, circ.CLK)
m.EndDefine()
```

Here's an example test we can write using the internal signals

```python
tester = fault.Tester(circ, circ.CLK)
tester.verilator_include("SimpleALU")
tester.verilator_include("ConfigReg")
tester.poke(circ.config_en, 1)
for i in range(0, 4):
tester.poke(circ.config_data, i)
tester.step(2)
tester.expect(
fault.WrappedVerilogInternalPort("top.SimpleALU_inst0.opcode",
m.Bits(2)),
i)
signal = tester.peek(
fault.WrappedVerilogInternalPort("top.SimpleALU_inst0.opcode",
m.Bits(2)))
tester.expect(
fault.WrappedVerilogInternalPort("top.SimpleALU_inst0.opcode",
m.Bits(2)),
signal)
tester.expect(
fault.WrappedVerilogInternalPort("top.SimpleALU_inst0.config_reg.Q",
m.Bits(2)),
i)
signal = tester.peek(
fault.WrappedVerilogInternalPort("top.SimpleALU_inst0.config_reg.Q",
m.Bits(2)))
tester.expect(
fault.WrappedVerilogInternalPort("top.SimpleALU_inst0.config_reg.Q",
m.Bits(2)),
signal)
```

Notice that the test must include the desired wrapped module with the statement
`tester.verilator_include("simple_alu")`. This is so the generated C harness
includes the verilator generated header. To expect or peek a port, you can use
`WrappedVerilogInternalPort` and provide the verilator C++ style path through
the instance hierarchy to the desired port.
75 changes: 68 additions & 7 deletions docs/actions.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ <h1 class="title"><code>fault.actions</code> module</h1>
<details class="source">
<summary>Source code</summary>
<pre><code class="python">from abc import ABC, abstractmethod
import fault
from fault.select_path import SelectPath


class Action(ABC):
Expand Down Expand Up @@ -50,9 +52,18 @@ <h1 class="title"><code>fault.actions</code> module</h1>
return cls(new_port, self.value)


def can_poke(port):
if isinstance(port, SelectPath):
port = port[-1]
if isinstance(port, fault.WrappedVerilogInternalPort):
return not port.type_.isinput()
else:
return not port.isinput()


class Poke(PortAction):
def __init__(self, port, value):
if port.isinput():
if not can_poke(port):
raise ValueError(f&#34;Can only poke inputs: {port.debug_name} &#34;
f&#34;{type(port)}&#34;)
super().__init__(port, value)
Expand All @@ -73,9 +84,18 @@ <h1 class="title"><code>fault.actions</code> module</h1>
return cls(new_port, self.format_str)


def is_output(port):
if isinstance(port, SelectPath):
port = port[-1]
if isinstance(port, fault.WrappedVerilogInternalPort):
return not port.type_.isoutput()
else:
return not port.isoutput()


class Expect(PortAction):
def __init__(self, port, value):
if port.isoutput():
if not is_output(port):
raise ValueError(f&#34;Can only expect on outputs: {port.debug_name} &#34;
f&#34;{type(port)}&#34;)
super().__init__(port, value)
Expand All @@ -84,7 +104,7 @@ <h1 class="title"><code>fault.actions</code> module</h1>
class Peek(Action):
def __init__(self, port):
super().__init__()
if port.isoutput():
if not is_output(port):
raise ValueError(f&#34;Can only peek on outputs: {port.debug_name} &#34;
f&#34;{type(port)}&#34;)
self.port = port
Expand Down Expand Up @@ -131,6 +151,41 @@ <h1 class="title"><code>fault.actions</code> module</h1>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="fault.actions.can_poke"><code class="name flex">
<span>def <span class="ident">can_poke</span></span>(<span>port)</span>
</code></dt>
<dd>
<section class="desc"></section>
<details class="source">
<summary>Source code</summary>
<pre><code class="python">def can_poke(port):
if isinstance(port, SelectPath):
port = port[-1]
if isinstance(port, fault.WrappedVerilogInternalPort):
return not port.type_.isinput()
else:
return not port.isinput()}</code></pre>
</details>
</dd>
<dt id="fault.actions.is_output"><code class="name flex">
<span>def <span class="ident">is_output</span></span>(<span>port)</span>
</code></dt>
<dd>
<section class="desc"></section>
<details class="source">
<summary>Source code</summary>
<pre><code class="python">def is_output(port):
if isinstance(port, SelectPath):
port = port[-1]
if isinstance(port, fault.WrappedVerilogInternalPort):
return not port.type_.isoutput()
else:
return not port.isoutput()}</code></pre>
</details>
</dd>
</dl>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
Expand Down Expand Up @@ -241,7 +296,7 @@ <h3>Inherited members</h3>
<summary>Source code</summary>
<pre><code class="python">class Expect(PortAction):
def __init__(self, port, value):
if port.isoutput():
if not is_output(port):
raise ValueError(f&#34;Can only expect on outputs: {port.debug_name} &#34;
f&#34;{type(port)}&#34;)
super().__init__(port, value)}</code></pre>
Expand Down Expand Up @@ -272,7 +327,7 @@ <h3>Inherited members</h3>
<pre><code class="python">class Peek(Action):
def __init__(self, port):
super().__init__()
if port.isoutput():
if not is_output(port):
raise ValueError(f&#34;Can only peek on outputs: {port.debug_name} &#34;
f&#34;{type(port)}&#34;)
self.port = port
Expand Down Expand Up @@ -300,7 +355,7 @@ <h3>Methods</h3>
<summary>Source code</summary>
<pre><code class="python">def __init__(self, port):
super().__init__()
if port.isoutput():
if not is_output(port):
raise ValueError(f&#34;Can only peek on outputs: {port.debug_name} &#34;
f&#34;{type(port)}&#34;)
self.port = port}</code></pre>
Expand Down Expand Up @@ -331,7 +386,7 @@ <h3>Inherited members</h3>
<summary>Source code</summary>
<pre><code class="python">class Poke(PortAction):
def __init__(self, port, value):
if port.isinput():
if not can_poke(port):
raise ValueError(f&#34;Can only poke inputs: {port.debug_name} &#34;
f&#34;{type(port)}&#34;)
super().__init__(port, value)}</code></pre>
Expand Down Expand Up @@ -525,6 +580,12 @@ <h1>Index</h1>
<li><code><a title="fault" href="index.html">fault</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="fault.actions.can_poke" href="#fault.actions.can_poke">can_poke</a></code></li>
<li><code><a title="fault.actions.is_output" href="#fault.actions.is_output">is_output</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
Expand Down
Loading

0 comments on commit 109ddce

Please sign in to comment.