Py4Hw User Guide> 1.1 Design Style¶

Next: 1.2 Object Oriented Approach¶

Hardware design tools typically combine different design styles.

  1. Graphical
    1. Structural
  2. Textual (HDL)
    1. Structural
    2. Register Transfer Level (RTL)
    3. Sequential Behavioural (Procedural Programming Languages). We also call it algorithmic, to avoid confusion with RTL sequential models.

Textual description of Hardware is done by Hardware description languages (HDL). Most popular ones are Verilog, SystemVerilog, VHDL, Chisel (based in Scala), and MyHDL (also based on Python).

In py4hw we promote the use of structural design style, by reusing as much as possible already designed and proven modules, but we also support RTL and sequential design styles.

In structural design styles a hardware module is build by instantiating other hardware modules and connecting them with wires.

Thus, to create a circuit, the hardware designer has to reuse existing available blocks and create new ones when needed.

By doing that, the design ends up being a set of blocks with a hierarchycal dependence. For instance, let A,B,C,D,E,F be preexisting logic blocks, and A being composed by B and C, and E being composed by A and F. If a hardware designer builds a circuit g reusing A and using a newly created block h using D and E. The hierarchical description of the circuit g would be the following.

g 
+- A
|  +- B
|  +- C
+--h
   +- D
   +- E
      +- A
      |  +- B
      |  +- C
      +- F

In this case we would say that g is the top level entity of the hierarchy

In [1]:
import py4hw
In [2]:
# Imagine the following example
sys = py4hw.HWSystem()

inc = py4hw.Wire(sys, 'inc', 1)
count = py4hw.Wire(sys, 'count', 8)
q = py4hw.Wire(sys, 'q', 8)

py4hw.Constant(sys, 'inc', 1, inc)
py4hw.Add(sys, 'counter', q, inc, count)
py4hw.Reg(sys, 'reg', d=count, enable=inc, q=q)

print('circuit created')
circuit created

The following code shows prints the hierarchy of the system

In [3]:
py4hw.debug.printHierarchy(sys)
HWSystem
 Constant
 Add
  Constant
  AddCarryIn
 Reg

Structural Design Style¶

Structural design consist on creating instances of other elements. In py4hw this is done in the constructor of the class that implements the circuit.

In [4]:
class CounterStructural(py4hw.Logic):
    def __init__(self, parent, name, inc, q):
        super().__init__(parent, name)
    
        self.addIn('inc', inc)
        self.addOut('q', q)
        
        count = py4hw.Wire(sys, 'count', 8)

        py4hw.Add(self, 'counter', q, inc, count)
        py4hw.Reg(self, 'reg', d=count, enable=inc, q=q)
In [5]:
sys = py4hw.HWSystem()

inc = py4hw.Wire(sys, 'inc', 1)
q = py4hw.Wire(sys, 'q', 8)

py4hw.Constant(sys, 'inc', 1, inc)

CounterStructural(sys, 'counter', inc, q)

print('circuit created')

sch = py4hw.Schematic(sys.children['counter'])
sch.draw()
circuit created

RTL Design Style¶

Not all the circuits can be described structurally.

Some circuit primitives (like and, and or gates and registers) are already described behaviourally in the py4hw library.

There are two types of behavioural descriptions: combinational and sequential.

Combinational circuits are modelled by implementing the propagate method.

Sequential circuits are modelled by implementing the clock method.

Since ports are dynamically created in the class constructor, to have them available in the clock or propagate methods they are typically also stored as members of the circuit class.

self.<port name> = self.addIn('<port name>', <port name>)
In [6]:
class CounterRTL(py4hw.Logic):
    def __init__(self, parent, name, inc, q):
        super().__init__(parent, name)
    
        self.inc = self.addIn('inc', inc)
        self.q = self.addOut('q', q)
        
        
    def clock(self):
        if (self.inc.get() == 1):
            self.q.prepare(self.q.get()+1)
        

We can create the circuit, and test it to see it is totally equivalent to the structural description

In [7]:
sys = py4hw.HWSystem()

inc = py4hw.Wire(sys, 'inc', 1)
q0 = py4hw.Wire(sys, 'q0', 8)
q1 = py4hw.Wire(sys, 'q1', 8)

py4hw.Sequence(sys, 'inc', [0, 0, 1], inc)

count0 = CounterStructural(sys, 'counter_structural', inc, q0)
count1 = CounterRTL(sys, 'counter_rtl', inc, q1)

print('circuit created')

wvf = py4hw.Waveform(sys, 'wvf', [inc,q0,q1])

sys.getSimulator().clk(20)
wvf.draw_wavedrom()
circuit created
Out[7]:

What about the Verilog implementation ?

In [8]:
rtl = py4hw.VerilogGenerator(count0)

print(rtl.getVerilogForHierarchy())
// This file was automatically created by py4hw Verilog generator
module CounterStructural (
	input clk,
	input  inc,
	output [7:0] q);
wire [7:0] w_count;

Add8_1_8 i_counter(.a(q),.b(inc),.r(w_count));
Reg8E i_reg(.clk(clk),.d(w_count),.e(inc),.q(q));
endmodule

// This file was automatically created by py4hw Verilog generator
module Add8_1_8 (
	input [7:0] a,
	input  b,
	output [7:0] r);
wire w_ci;

assign w_ci = 0;
assign r = a + b + w_ci;
endmodule

// This file was automatically created by py4hw Verilog generator
module Reg8E (
	input clk,
	input [7:0] d,
	input  e,
	output [7:0] q);
reg [7:0] rq = 0;
always @(posedge clk)
if (e == 1)
begin
   rq <= d;
end
assign q = rq;
endmodule

In [9]:
rtl = py4hw.VerilogGenerator(count1)

print(rtl.getVerilogForHierarchy())
transpiling sequential /HWSystem[HWSystem]/CounterRTL[counter_rtl]
// This file was automatically created by py4hw Verilog generator
module CounterRTL (
	input clk,
	input  inc,
	output  reg [7:0] q);
// Code generated from clock method
// wire/variable declaration
// initial
initial
begin
end
// process
always @(posedge clk)
begin
    if (inc==1)
    begin
        q<=q+1;
    end
end
endmodule

Algorithmic Design Style¶

A more common design style for Software programmers are sequential programs defined by procedural programming languages.

In this case, we use sequential cirtuits (implementing the clock method) with python coroutines.

The sequential algorithm is described in the run method, and everything that is executed between two invocations of the yield statement happen in during the same clock cycle.

We currently do not support Hardware synthesis from this design style, but it is very powerful for simulation. You can use yield from to implement functions, increasing the complexity of the simulation stimuli.

The example below is the same circuit expressed in a Sequential design style.

In [15]:
class CounterSeq(py4hw.Logic):
    def __init__(self, parent, name, inc, q):
        super().__init__(parent, name)
    
        self.inc = self.addIn('inc', inc)
        self.q = self.addOut('q', q)
        
        self.vinc = 0
        self.vq = 0
        
        self.co = self.run()
        
    def clock(self):
        next(self.co)
        
    def run(self):
        while(True):
            if (self.inc.get()):
                self.q.prepare(self.q.get() + 1)
            yield

You can easily verify that the behaviour of all 3 circuits described with different design styles is the same.

In [16]:
sys = py4hw.HWSystem()

inc = py4hw.Wire(sys, 'inc', 1)
q0 = py4hw.Wire(sys, 'q0', 8)
q1 = py4hw.Wire(sys, 'q1', 8)
q2 = py4hw.Wire(sys, 'q2', 8)

py4hw.Sequence(sys, 'inc', [0, 0, 1], inc)

count0 = CounterStructural(sys, 'counter_structural', inc, q0)
count1 = CounterRTL(sys, 'counter_rtl', inc, q1)
count2 = CounterSeq(sys, 'counter_seq', inc, q2)

print('circuit created')

wvf = py4hw.Waveform(sys, 'wvf', [inc,q0,q1,q2])

sys.getSimulator().clk(20)

print('Simulation complete')
wvf.draw_wavedrom()
circuit created
Simulation complete
Out[16]:

Summary¶

  • py4hw supports 3 design styles: structural, behavioural (RTL), algorithmic
  • structural circuits instantiate their logic in the constructor
  • behavioral combinational circuits implement the propagate method
  • behavioral sequential circuits implement the clock method
  • algorithmic circuits implement the run method as a coroutine (together with clock)
In [ ]: