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