MyHDL, de Python al silicio

img/Afiche.png
por Martín Gaitán
PyCon Argentina 2012
@tin_nqn_

Quien soy

El desafío

img/miedo.jpg

A modo de intro

¿Dónde estamos?

img/esta_aqui.jpg

Programar HW != diseñar HW

Pero... ¿cómo se diseña un micro?

.

¿Software? No era hardware?

FPGAs: Field Programmable Gates Array

El camaleón mamá, el camaleón, cambia de colores según la ocasión...

Chico Novarro

Por ejemplo

img/fpga.jpg

Sólo para probar?

.

La burocracia

img/flow.jpg

Contras

  • Muchos lenguajes

  • Distintos equipos

  • Alto nivel: Matlab

  • RTL: System C, Verilog, VHDL

  • Testing lento y malo

Un workflow pythonico

img/flow-myhdl.png

.

Y qué corno es MyHDL ?

img/myhdl_logo_256.png

Veamos

  • Es un framework HDL en Python

  • Incluye tipos de datos, helpers, conversores y simulador

  • Permite unificar algoritmo, RTL y verificación en un mismo entorno!

  • Convierte (con restricciones) a VHDL o Verilog sintetizable

  • Permite cosimular Verilog (VHDL en desarrollo)

  • Gratis y Libre (LGPL)

  • Buena documentación y comunidad

  • Y es Python, the glue language!

.

Ejemplo

Un multiplexor de dos canales

img/mux.png

VHDL

The ugly way

library ieee ;
use ieee . std logic 1164 . all ;

entity mux is
    port (
    a, b : in std logic vector (3 downto 0);
    s : in std logic ;
    o : out std logic vector (3 downto 0));
end mux;
architecture behavior of mux is
begin behavior
    o <= a when s = '0' else b;
end behavior

Contras

  • Requiere declarar la "entidad" (entradas y salidas) y comportamiento

  • Tipado estático: requiere declarar tipo de entradas

  • Verbósico

  • Sintáxis horrible

  • No ortogonal

  • No hay testing fácil

.

Myhdl's way

def mux(s, o, a, b):
    """
    2-channels N-bits multiplexor

    a, b: generic bits input channels
    o: output vector
    s: channel selector
    """

    @always_comb
    def logic():
        if s == 0:
            o.next = a
        else:
            o.next = b
    return logic

Pros

  • La entidad se determina por introspección (cuando se instancia)

  • Python es dinámico ;-)

  • Simple is better than complex

Expliquemos

Los generadores

@always_comb

cuando cambie cualquier señal de entrada

@always

cuando cambie las que le indiquemos

@instance

generador adhoc (se usa en testbench)

.

Bueno, enchufemos!

¿Y cómo echufamos?

Signal (a.k.a "cablecitos")

>>> bus = Signal(0)
>>> bus.val
0
>>> bus.next = 1
>>>

Pero ...

El hardware es duro: tiene límites físicos ¿cuántos bits tiene ese bus?

>>> val = intbv(1, min=0, max=15)
>>> len(val)
4
>>> bus = Signal(val)

Ahora sí, enchufemos!

Cómo ? Hagamos un testbench

def testBench():

I0, I1 = [Signal(intbv(random.randint(0, 255))[32:]) for i in range(2)]
O = Signal(intbv(0)[32:])
S = Signal(intbv(0, min=0, max=2))

mux_inst = mux (S, O, I0, I1)

@instance
def stimulus():
    header =  "%-6s|%-6s|%-6s|%-6s|%-6s" % ('time', 'I0', 'I1', 'S', 'O')
    print header + '\n' + '-' * len(header)
    while True:
        S.next = intbv(random.randint(0, 1))[1:]
        I0.next, I1.next = [intbv(random.randint(0, 255))[32:]
                            for i in range(2)]
        print "%-6s|%-6s|%-6s|%-6s|%-6s" % (now(), I0, I1, S, O)
        yield delay(5)

return mux_inst, stimulus

Y simulemos

sim = Simulation(testBench())
sim.run(20)

Acá es cuando no funciona

Demo

  • en Ipython:

    %run run.py
    %run ejemplo1.py

El resultado

time  |I0    |I1    |S     |O
----------------------------------
0     |35    |96    |0     |0
5     |164   |254   |1     |254
10    |132   |60    |0     |132
15    |32    |138   |0     |32
20    |15    |112   |1     |112
<class 'myhdl._SuspendSimulation'>: Simulated 20 timesteps

.

Pero entonces...

¿Se verifica con prints? Buuhh!

Un print sofisticado: generar formas de onda ().vcd

tb_4_sim = traceSignals(testBench)
sim = Simulation(tb_4_sim)
sim.run(20)

Y se ven

Con GTKWave (por ejemplo)

img/vcd.png

Pero mejor es hacer test de verdad!

class MuxTest(unittest.TestCase):

    def setUp(self):
        self.channels = [Signal(intbv(random.randint(0, 255))[32:]) for i in range(2)]
        self.O = Signal(intbv(0)[32:])
        self.S = Signal(intbv(0, min=0, max=2))
        self.mux_inst = mux(self.S, self.O, self.channels[0], self.channels[1])

    def test_starts_in_channel_0(self):
        yield delay(1)
        Simulation( self.mux_inst )
        self.assertEqual(self.channels[0].val, self.O.val)

    def test_channel_1_when_select_is_1(self):
        self.S.next = intbv(1)
        yield delay(1)
        Simulation( self.mux_inst )
        self.assertEqual(self.channels[1].val, self.O.val)

Convirtiendo pa'sintetizar

mux_inst = toVHDL(mux, S, O, I0, I1)
mux_inst = toVerilog(mux, S, O, I0, I1)

Un proyectito

Para mi última materia: hice un procesador DLX/MIPS en 3 semanas

https://github.com/mgaitan/pymips

img/dlx.png

Conclusiones

Preguntas ?

for p in preguntas:
    try:
        responder(p)
    except NiPutaIdea:
        sonreir_y_hacerse_el_gil()

La hora referí

Esta y otras charlas están en...