|
1 | | -""" |
2 | | -Example 1: A simple combination logic block example. |
3 | | -
|
4 | | -This example declares a block of hardware with three one-bit inputs, (a,b,c) and two |
5 | | -one-bit outputs (sum, cout). The logic declared is a simple one-bit adder and the |
6 | | -definition uses some of the most common parts of PyRTL. The adder is then simulated on |
7 | | -random data, the wave form is printed to the screen, and the resulting trace is compared |
8 | | -to a "correct" addition. If the result is correct then a 0 is returned, else 1. |
9 | | -""" |
10 | | - |
| 1 | +# # Example 1: A simple combination logic block example. |
| 2 | +# |
| 3 | +# This example declares a block of hardware with three one-bit inputs, (`a`,`b`,`c`) and |
| 4 | +# two one-bit outputs (`sum`, `cout`). The logic declared is a simple one-bit adder and |
| 5 | +# the definition uses some of the most common parts of PyRTL. The adder is then |
| 6 | +# simulated on random data, the wave form is printed to the screen, and the resulting |
| 7 | +# trace is compared to a "correct" addition. |
11 | 8 | import random |
12 | 9 |
|
13 | 10 | import pyrtl |
14 | 11 |
|
15 | | -# The basic idea of PyRTL is to specify the component of a some hardware block through |
16 | | -# the declaration of wires and operations on those wires. The current working block, an |
17 | | -# instance of a class devilishly named "Block", is implicit in all of the below code -- |
18 | | -# it is easiest to start with the way wires work. |
19 | | - |
20 | | -# --- Step 1: Define Logic ------------------------------------------------- |
21 | | - |
22 | | -# One of the most fundamental types in PyRTL is the "WireVector" which acts very much |
| 12 | +# The basic idea of PyRTL is to specify a hardware block by defining wires and the |
| 13 | +# operations performed on those wires. The current working block, an instance of a class |
| 14 | +# devilishly named `Block`, is implicit in all of the code below -- it is easiest to |
| 15 | +# start with the way wires work. |
| 16 | +# |
| 17 | +# ## Step 1: Define Logic |
| 18 | +# |
| 19 | +# One of the most fundamental types in PyRTL is the `WireVector` which acts very much |
23 | 20 | # like a Python list of 1-bit wires. Unlike a normal list, though, the number of bits is |
24 | 21 | # explicitly declared. |
25 | 22 | temp1 = pyrtl.WireVector(bitwidth=1, name="temp1") |
26 | 23 |
|
27 | | -# Both arguments are in fact optional and default to a bitwidth of 1 and a unique name |
28 | | -# generated by PyRTL starting with 'tmp' |
| 24 | +# Both arguments are optional and default to a `bitwidth` of 1 and a unique `name` |
| 25 | +# generated by PyRTL that starts with `tmp`. |
29 | 26 | temp2 = pyrtl.WireVector() |
30 | 27 |
|
31 | | -# Two special types of WireVectors are Input and Output, which are used to specify an |
| 28 | +# `Input` and `Output` are two special types of `WireVectors`, which specify the |
32 | 29 | # interface to the hardware block. |
33 | | -a, b, c = pyrtl.Input(1, "a"), pyrtl.Input(1, "b"), pyrtl.Input(1, "c") |
34 | | -sum, carry_out = pyrtl.Output(1, "sum"), pyrtl.Output(1, "carry_out") |
| 30 | +a = pyrtl.Input(1, "a") |
| 31 | +b = pyrtl.Input(1, "b") |
| 32 | +c = pyrtl.Input(1, "c") |
| 33 | + |
| 34 | +sum = pyrtl.Output(1, "sum") |
| 35 | +carry_out = pyrtl.Output(1, "carry_out") |
35 | 36 |
|
36 | 37 | # Okay, let's build a one-bit adder. To do this we need to use the assignment operator, |
37 | | -# which is '<<='. This takes an already declared wire and "connects" it to some other |
38 | | -# already declared wire. Let's start with the sum bit, which is of course just the xor |
39 | | -# of the three inputs |
| 38 | +# which is `<<=`. This takes an already declared wire and "connects" it to another |
| 39 | +# already declared wire. Let's start with the `sum` bit, which is of course just the xor |
| 40 | +# of the three inputs: |
40 | 41 | sum <<= a ^ b ^ c |
41 | 42 |
|
42 | | -# The carry_out bit would just be "carry_out <<= a & b | a & c | b & c" but let's break |
43 | | -# than down a bit to see what is really happening. What if we want to give names to the |
44 | | -# partial signals in the middle of that computation? When you take "a & b" in PyRTL, |
45 | | -# what that really means is "make an AND gate, connect one input to 'a' and the other to |
46 | | -# 'b' and return the result of the gate". The result of that AND gate can then be |
47 | | -# assigned to temp1 or it can be used like any other Python variable. |
48 | | - |
49 | | -temp1 <<= a & b # connect the result of a & b to the pre-allocated wirevector |
| 43 | +# The `carry_out` bit would just be `carry_out <<= a & b | a & c | b & c` but let's |
| 44 | +# break that down a bit to see what is really happening. What if we want to give names |
| 45 | +# to the intermediate signals in the middle of that computation? When you take `a & b` |
| 46 | +# in PyRTL, what that really means is "make an AND gate, connect one input to `a` and |
| 47 | +# the other to `b` and return the result of the gate". The result of that AND gate can |
| 48 | +# then be assigned to `temp1` or it can be used like any other Python variable. |
| 49 | +temp1 <<= a & b # connect the result of a & b to the pre-allocated WireVector |
50 | 50 | temp2 <<= a & c |
51 | 51 | temp3 = b & c # temp3 IS the result of b & c (this is the first mention of temp3) |
52 | 52 | carry_out <<= temp1 | temp2 | temp3 |
53 | 53 |
|
54 | | -# You can access the working block through pyrtl.working_block(), and for most things |
55 | | -# one block is all you will need. Example 2 discusses this in more detail, but for now |
56 | | -# we can just print the block to see that in fact it looks like the hardware we |
57 | | -# described. The format is a bit weird, but roughly translates to a list of gates (the |
58 | | -# 'w' gates are just wires). The ins and outs of the gates are printed |
59 | | -# 'name'/'bitwidth''WireVectorType' |
60 | | - |
| 54 | +# You can access the working block through `working_block()`, and for most things one |
| 55 | +# block is all you will need. Example 2 discusses this in more detail, but for now we |
| 56 | +# can just print the block to see that in fact it looks like the hardware we described. |
| 57 | +# The format is a bit weird, but roughly translates to a list of gates (the `w` gates |
| 58 | +# are just wires). The ins and outs of the gates are formatted as |
| 59 | +# `{name}/{bitwidth}{WireVectorType}`. |
61 | 60 | print("--- One Bit Adder Implementation ---") |
62 | 61 | print(pyrtl.working_block()) |
63 | | -print() |
64 | | - |
65 | | -# --- Step 2: Simulate Design ----------------------------------------------- |
66 | 62 |
|
| 63 | +# ## Step 2: Simulate Design |
| 64 | +# |
67 | 65 | # Okay, let's simulate our one-bit adder. |
68 | | - |
69 | 66 | sim = pyrtl.Simulation() |
70 | 67 |
|
71 | | -# Now all we need to do is call "sim.step" to simulate each clock cycle of our design. |
72 | | -# We just need to pass in some input each cycle, which is a dictionary mapping inputs |
73 | | -# (the *names* of the inputs, not the actual Input instances) to their value for that |
74 | | -# signal each cycle. In this simple example, we can just specify a random value of 0 or |
75 | | -# 1 with Python's random module. We call step 15 times to simulate 15 cycles. |
76 | | - |
| 68 | +# Now all we need to do is call `step()` to simulate each clock cycle of our design. We |
| 69 | +# just need to pass in some input each cycle, which is a dictionary mapping inputs (the |
| 70 | +# *names* of the inputs, not the actual instances of `Input`) to their value for that |
| 71 | +# signal in the current cycle. In this simple example, we can just specify a random |
| 72 | +# value of 0 or 1 with Python's random module. We call step 15 times to simulate 15 |
| 73 | +# cycles. |
77 | 74 | for _cycle in range(15): |
78 | 75 | sim.step( |
79 | | - { |
80 | | - "a": random.choice([0, 1]), |
81 | | - "b": random.choice([0, 1]), |
82 | | - "c": random.choice([0, 1]), |
83 | | - } |
| 76 | + {"a": random.randrange(2), "b": random.randrange(2), "c": random.randrange(2)} |
84 | 77 | ) |
85 | 78 |
|
86 | 79 | # Now all we need to do is print the trace results to the screen. Here we use |
87 | | -# "render_trace" with some size information. |
88 | | -print("--- One Bit Adder Simulation ---") |
| 80 | +# `render_trace()` with some size information. |
| 81 | +print("\n--- One Bit Adder Simulation ---") |
89 | 82 | sim.tracer.render_trace(symbol_len=2) |
90 | 83 |
|
91 | 84 | a_value = sim.inspect(a) |
92 | 85 | print("The latest value of 'a' was: ", a_value) |
93 | 86 |
|
94 | | -# --- Step 3: Verification of Simulated Design --------------------------------------- |
95 | | - |
96 | | -# Now finally, let's check the trace to make sure that sum and carry_out are actually |
| 87 | +# ## Step 3: Verification of Simulated Design |
| 88 | +# |
| 89 | +# Finally, let's check the trace to make sure that `sum` and `carry_out` are actually |
97 | 90 | # the right values when compared to Python's addition operation. Note that all the |
98 | 91 | # simulation is done at this point and we are just checking the waveform, but there is |
99 | 92 | # no reason you could not do this at simulation time if you had a really long-running |
100 | 93 | # design. |
101 | | - |
102 | 94 | for cycle in range(15): |
103 | | - # Note that we are doing all arithmetic on values, NOT wirevectors, here. We can add |
104 | | - # the inputs together to get a value for the result |
| 95 | + # Note that we are doing all arithmetic on values, NOT `WireVectors` here. We can |
| 96 | + # add the inputs together to get a value for the result: |
105 | 97 | add_result = ( |
106 | 98 | sim.tracer.trace["a"][cycle] |
107 | 99 | + sim.tracer.trace["b"][cycle] |
108 | 100 | + sim.tracer.trace["c"][cycle] |
109 | 101 | ) |
110 | | - # We can select off the bits and compare |
| 102 | + # We can select off the bits and compare: |
111 | 103 | python_sum = add_result & 0x1 |
112 | 104 | python_cout = (add_result >> 1) & 0x1 |
113 | 105 | if ( |
114 | 106 | python_sum != sim.tracer.trace["sum"][cycle] |
115 | 107 | or python_cout != sim.tracer.trace["carry_out"][cycle] |
116 | 108 | ): |
117 | | - print("This Example is Broken!!!") |
118 | | - exit(1) |
| 109 | + msg = "This Example is Broken!!!" |
| 110 | + raise Exception(msg) |
0 commit comments