from pygmyhdl import *

@chunk
def dff(clk_i, d_i, q_o):
    '''
    Inputs:
      clk_i: Rising edge on this input stores data on d_i into q_o.
      d_i: Input that brings new data into the flip-flop:
    Outputs:
      q_o: Output of the data stored in the flip-flop.
    '''
    @seq_logic(clk_i.posedge)
    def logic():
        q_o.next = d_i

"""
Explanation: Note: If you're reading this as a static HTML page, you can also get it as an
executable Jupyter notebook here.
Hierarchy and Abstraction and Ursidae, Oh My!
Those are big, scary words often associated with FPGA designs, but with simple meanings:
Hierarchy: "See that big, complex thing? It's actually made of small, simple things.
And those small, simple things are made of even smaller, simpler things."
Abstraction: "See that thing? It does something. I don't care how it does it.
I just need to know what it does."
Ursidae: This is the scientific family name for bears. They have nothing to do with this chapter.
I just needed it to make the whole Wizard of Oz reference work.
Take a car as an example. It consists of a body and a drive train. The drive train
is made up of an engine, transmission, drive shaft, axles and wheels.
And the engine is made up of a block, pistons, fuel injectors, etc.
And on and on.
That's hierarchy.
Then think about somebody who's putting an engine in a car.
He needs to know how big the engine is, how to feed gas to it, and where the
spinny thing is that attaches to the drive shaft and makes the wheels go round.
All the stuff about piston rings and camshafts and coolant channels
are details he can ignore.
That's abstraction.
And what about Ursidae?
Bears have nothing to do with this.
I told you that already.
Just let it go.
The role of hierarchy and abstraction in digital design is best understood
by going through a concrete example.
And what's the best example we have at this point?
The LED Blinker Revisited
Let's rebuild the LED blinker from our
previous example
as a hierarchy of smaller pieces of digital logic.
I'll do this in two phases:

Phase 1: I'll break the design into smaller pieces. Then I'll break those pieces into
  even smaller pieces and continue the process until I've reached the lowest level
  I think is reasonable.
Phase 2: I'll take each piece at the lowest level and describe it using MyHDL.
  Then, I'll interconnect those MyHDL code fragments and use them to build the
  next higher level.
  And I'll proceed like that until I've got the entire LED blinker built in MyHDL.

This process - break it down starting from the top, then rebuild it starting from the bottom - is
a common technique used in software design.
Since building hardware using an HDL is similar to writing software, it's no surprise
the pattern works here.
Break Down
Let's start by showing the LED blinker at it's highest level of abstraction:
it takes a clock as input and generates a single output that turns an LED on and off:
<img alt="Highest level of abstraction for an LED blinker." src="lvl_led_blinker.png" width=500/>
At the next level of abstraction, we already know the blinker is built from an $N$-bit
counter with the LED output driven from the counter's MSB.
<img alt="Second highest level of abstraction for an LED blinker." src="lvl_counter.png" width=500/>
Going down another level, a counter can be constructed from an $N$-bit
register
and an $N$-bit binary adder.
The register holds a value $V$ and, on every pulse of the clock input, that value
is replaced by $V + 1$.
<img alt="Third highest level of abstraction for an LED blinker." src="lvl_reg_adder.png" width=550/>
The register consists of $N$ individual
D flip-flops
each of which stores a single bit of the register value that is updated when there is a rising
edge of the clock input:
<img alt="Individual bits of a register." src="lvl_dff.png" width=400/>
The adder is also composed of $N$ subcomponents called
full-adder bits.
The $k$-th full-adder bit takes the $k$-th bit from each of the numbers to be added along with
a carry bit from the preceding stage of the adder.
It combines these to compute the $k$-th sum bit and a carry bit for the next stage of the adder.
In the figure below, I've replaced the a input with the value 0...001 because the
adder is being used to increment the value on the b input.
I've also placed a 0 on the carry input into the first bit of the adder.
<img alt="Interconnection of full-adder bits." src="lvl_adder.png" width=350/>
Finally, each full-adder bit is built from a
collection of primitive logic gates [1] like this:
<img alt="Full-adder bit gates." src="lvl_adder_bit.png" width=200/>
And that's the complete breakdown of the LED blinker.
Now it's time to build it back up using MyHDL.
Build Up
I broke the LED blinker down to its most basic components,
now I'll start at the lowest level of the hierarchy to build it back up.
<img alt="LED Blinker hierarchy" src="blinker_hierarchy.png" width=200/>
Here's the MyHDL code for a single D flip-flop. It's a simple piece of
sequential logic that copies its input to its output whenever there is a rising
edge on the clock input:
End of explanation
"""


@chunk
def register(clk_i, d_i, q_o):
    for k in range(len(d_i)):
        dff(clk_i, d_i.o[k], q_o.i[k])

"""
Explanation: Now I can build a register by instantiating multiple dff modules in a loop:
End of explanation
"""


initialize()  # Initialize for simulation.

# Create clock signal and 8-bit register input and output buses.
clk = Wire(name='clk')
data_in = Bus(8, name='data_in')
data_out = Bus(8, name='data_out')

# Instantiate a register and attach the clock and I/O buses.
register(clk_i=clk, d_i=data_in, q_o=data_out)

# Apply random 8-bit integers to the register input along with
# rising and falling edges of the clock signal.
from random import randint  # Random integer function.
def test_bench():
    # Apply ten random inputs to the register.
    for i in range(10):
        data_in.next = randint(0,256)  # Set register input to a random 8-bit value.
        clk.next = 0  # Lower clock signal to 0.
        yield delay(1)  # Wait for one unit of simulation time.
        clk.next = 1  # Then raise the clock signal to 1 ...
        yield delay(1)  # ... and wait one more time unit. The register output should change.
        
# Pass the test bench function to the simulator function to run the simulation.
simulate(test_bench())

# View the results of the simulation.
show_waveforms()

"""
Explanation: The loop count is set by the number of bits in the incoming data bus, d_i.
During each loop iteration, a D flip-flop is created.
The d_i.o[k] notation [2] says that the $k$-th output bit of
the d_i bus is used to drive the data input of the $k$-th flip-flop.
In a similar manner, the q_o.i[k] notation says that the output of the $k$-th
flip-flop will drive the $k$-th input bit of the q_o bus.
In this way, the register input bus is applied to the individual flip-flops, and the
flip-flop outputs collectively drive the register output bus.
To test the register, I'll connect it to a clock signal and some
input and output buses [3].
Then, I'll use a simple
test bench function
to apply random values to the
register input and see if those values propagate to the register output:
End of explanation
"""


@chunk
def full_adder_bit(a_i, b_i, c_i, s_o, c_o):
    '''
    Inputs:
      a_i, b_i: Inputs from i-th bit of a and b values.
      c_i: Input from carry output of (i-1)-th adder stage.
    Outputs:
      s_o: Output of i-th sum bit.
      c_o: Carry output to the (i+1)-th adder stage.
    '''
    @comb_logic
    def logic():
        # Exclusive-OR (^) the inputs to create the sum bit.
        s_o.next = a_i ^ b_i ^ c_i
        # Generate a carry output if two or more of the inputs are 1.
        # This uses the logic AND (&) and OR (|) operators.
        c_o.next = (a_i & b_i) | (a_i & c_i) | (b_i & c_i)

"""
Explanation: From the logic waveforms, you can see the value on the data_in bus is transferred to the
data_out bus whenever a rising edge occurs on the clk signal.
The register appears to be working, so I'll move on to begin implementing the adder
branch of the blinker hierarchy.
Here's the MyHDL for the combinatorial logic of a single full-adder bit:
End of explanation
"""


initialize()  # Initialize for a new simulation.

# Declare input and output signals for the full-adder bit.
a_i, b_i, c_i = Wire(name='a_i'), Wire(name='b_i'), Wire(name='c_i')
sum_o, c_o = Wire(name='sum_o'), Wire(name='c_o')

# Instantiate a full-adder bit with the I/O connections.
full_adder_bit(a_i, b_i, c_i, sum_o, c_o)

# Simulate the full-adder bit operation for every possible combination
# of the a_i, b_i and c_i inputs.
exhaustive_sim(a_i, b_i, c_i)

# Show the response of the full-adder bit to the inputs.
show_text_table()

"""
Explanation: To test the full-adder bit, I'll apply every possible combination of the three inputs and
see how it reacts:
End of explanation
"""


@chunk
def adder(a_i, b_i, s_o):
    '''
    Inputs:
      a_i, b_i: Numbers to be added.
    Outputs:
      s_o: Sum of a_i and b_i inputs.
    '''
    
    # Create a bus for the carry bits that pass from one stage to the next.
    # There is one more carry bit than the number of adder stages in order
    # to drive the carry input of the first stage. 
    c = Bus(len(a_i)+1)
    
    # Set the carry input to the first stage of the adder to 0.
    c.i[0] = 0
    
    # Use the length of the a_i input bus to set the loop counter.
    for k in range(len(a_i)):
        
        # The k-th bit of the a_i and b_i buses are added with the
        # k-th carry bit to create the k-th sum bit and the
        # carry output bit. The carry output is the 
        # carry input to the (k+1)-th stage.
        full_adder_bit(a_i=a_i.o[k], b_i=b_i.o[k], c_i=c.o[k], s_o=s_o.i[k], c_o=c.i[k+1])

"""
Explanation: You can compare the table of values above to this
full-adder truth-table
to verify it's working correctly.
Just like the register, I can build a complete adder by iteratively
instantiating the full-adder bit:
End of explanation
"""


initialize()  # Once again, initialize for a new simulation.

# Declare 8-bit buses for the two numbers to be added and the sum.
a = Bus(8, name='a')
b = Bus(8, name='b')
s = Bus(8, name='sum')

# Instantiate an adder and connect the I/O buses.
adder(a, b, s)

# Simulate the adder's output for 20 randomly-selected inputs.
random_sim(a, b, num_tests=20)

# Show a table of the adder output for each set of inputs.
show_text_table()

"""
Explanation: I won't even try to do an exhaustive test of the adder because with eight-bit
input buses, that would take $2^8 \times 2^8 = $ 65,536 simulation cycles
and nobody would even look at a fraction of the output any way.
Instead, I'll just check the adder's output for a few randomly-selected inputs:
End of explanation
"""


@chunk
def counter(clk_i, cnt_o):
    '''
    Inputs:
      clk_i: Counter increments on the rising edge of the clock.
    Outputs:
      cnt_o: Counter value.
    '''
    # The length of the counter output determines the number of counter bits.
    length = len(cnt_o)
    
    one = Bus(length, init_val=1)  # A constant bus that carries the value 1.
    next_cnt = Bus(length)         # A bus that carries the next counter value.
    
    # Add one to the current counter value to create the next value.
    adder(one, cnt_o, next_cnt)
    
    # Load the next counter value into the register on a rising clock edge.
    register(clk_i, next_cnt, cnt_o)

"""
Explanation: You can manually check the table results to see the adder is working correctly.
(Remember that the adder's output is only eight bits, so it will roll over to 0
upon reaching 256. Therefore, take the $\bmod{256}$ of the sum of the inputs and compare
that to the adder's output. For example, $(72 + 213) \bmod{256} = (285) \bmod{256} = 29$.)
Now I can move up another level in the hierarchy and build a counter from the adder and register:
End of explanation
"""


@chunk
def blinker(clk_i, led_o, length):
    '''
    Inputs:
      clk_i:  This is a clock signal input.
      length: This is the number of bits in the counter that generates the led_o output.
    Outputs:
      led_o:  This is an output signal that drives an LED on and off.
    '''
    cnt = Bus(length, name='cnt')  # Declare the counter bus with the given length.
    counter(clk_i, cnt)  # Instantiate a counter of the same length.
    
    # Attach the MSB of the counter bus to the LED output.
    @comb_logic
    def output_logic():
        led_o.next = cnt[length-1]

"""
Explanation: Having finally reached the top of the hierarchy, I'll use the counter to complete the implementation of the LED blinker:
End of explanation
"""


initialize()                 # Initialize for simulation.
clk = Wire(name='clk')       # Declare the clock input.
led = Wire(name='led')       # Declare the LED output.
blinker(clk, led, 3)         # Instantiate a three-bit blinker and attach I/O signals.
clk_sim(clk, num_cycles=16)  # Apply 16 clock pulses.
show_waveforms()             # Look at the waveforms.

"""
Explanation: Let's check the operation by simulating a small LED blinker as we did in the previous example:
End of explanation
"""


toVerilog(blinker, clk_i=clk, led_o=led, length=22)

"""
Explanation: A blinker with a three-bit counter should output an LED signal that runs
at an eighth of the incoming clock frequency.
The waveforms show that's what is happening.
Since the simulation seems to work, it's time to convert a larger version of the blinker into Verilog
after which it can be compiled and run on an iCEstick.
(Because this LED blinker uses a hierarchical description that results in a lot of
smaller, individual components at the lower levels, it will take longer to generate
the Verilog than the previous example. Be patient!)
End of explanation
"""


with open('blinker.pcf', 'w') as pcf:
    pcf.write(
'''
set_io led_o 99
set_io clk_i 21
'''
    )

"""
Explanation: Whew, done!
You may notice a few warnings about undriven signals, but don't worry about those.
The next step is to write a file with the blinker pin assignments for the iCEstick board.
The 12 MHz clock enters through pin 21 of the FPGA, and LED D1 is driven by pin 99.
End of explanation
"""


!yosys -q -p "synth_ice40 -blif blinker.blif" blinker.v
!arachne-pnr -q -d 1k -p blinker.pcf blinker.blif -o blinker.asc
!icepack blinker.asc blinker.bin

"""
Explanation: The following commands will compile the Verilog and pin assignments into a bitstream file:
End of explanation
"""


!iceprog blinker.bin

"""
Explanation: Finally, the bitstream is ready to download into the iCEstick:
End of explanation
"""


