Hardware myhdl python
From CInLUG
Conteúdo |
Introdução
Atualmente, o desenvolvimento de Hardware [1] tem sido muito mais intenso e rápido. Alguns técnicas surgiram ao longo dos anos, com a finalidade de acelerar o processo de desenvolvimento de Chips. O FPGA [2] - Hardware reprogramável criado pela Xilinx Inc [3] - foi uma delas. A facilidade e flexibilidade dos FPGAs são tão grandes que o atual desenvolvimento hardware utiliza FPGAs tanto como produto final, quanto como fase de desenvolvimento de grandes projetos que serão produzidos em silício.
Os FPGAs são programados através de linguagens de descrição de hardware ou HDLs [4] as quais descrevem formalmente o comportamento do dispositivo, onde as mais conhecidas são: VHDL [5] e Verilog [6]. Existem várias liguagens que abstraem VHDL ou Verilog visando facilitar ainda mais a vida do projetista.
Neste artigo falaremos de Python [7] como facilitador e teremos como foco uma biblioteca de desenvolvimento de hardware em Python chamada MyHDL [8]. Como diria o autor desta biblioteca: The goal of the MyHDL project is to empower hardware designers with the elegance and simplicity of the Python language Jan Decaluwe.
Preparando o ambiente
Para instalar MyHDL em qualquer máquina que rode Linux e que tenha Python >= 2.4 basta a execução dos seguintes passos:
- - Baixar o pacote myhdl para a instalação através deste link;
- - Executar os seguintes comandos:
> cd <diretório onde encontra-se o pacote baixado> > tar -xvzf myhdl-x.x.x.tar.gz > cd myhdl-x.x.x > python setup.py install > cd myhdl/test > python test_all.py ...vai rodar uns 2 centos de testes... >
Pronto para o simples uso de MyHDL seu ambiente já está funcionando. Ao longo do texto algumas instruções de instalação serão passadas, para possibilitar o uso mais complexo de MyHDL.
| Durante todo o texto estou supondo que você está usando Linux, nenhuma instrução de instalação do Windows, do Mac ou de qualquer outro sistema operacional é dada pelo simples fato de eu não saber ou não usar. Peço desculpas. |
Estrutura
Bem...a figura abaixo mostra em resumo de como é o desenvolvimento de hardware usando Python MyHDL. O processo inicia-se quando o desenvolverdor (respeitando as regras de Engenharia de Software) escreve os testes unitários antes dos módulos (Artefato 1). Agora que os testes estão prontos e bem feitos, pode-se iniciar o desenvolvimento do módulo. Após a conclusão do módulo (Artefato 2), os testes unitários podem ser executados e avaliados, também podem ser feitas simulações as quais podem ter como resultado waveforms, valores impressos na tela, o preenchimento de arquivos, entres muitas outras possibilidades. Caso o os testes sejam mal sucedidos deve-se corrigir os erros e testar novamente (claro!!!), caso contrário pode-se passar para a próxima fase que é a conversão de Python para Verilog - esta fase é muito simples não necessitando de muito esforço. Agora, após a conversão, temos o código em verilog gerado (Artefato 3). Para testarmos o módulo em Verilog teremos que usar um artifício conhecido como Co-Simulação o qual nada mais é (neste caso) do que uma simulação ou execução de Python e Verilog em conjunto. Com a Co-Simulação poderemos rodar novamente os casos de testes (Artefato 1) ainda em Python para validar o novo código em Verilog (Artefato 3). Depois de validado o projeto está pronto para ser utilizado em FPGA. Pronto!
Técnicas de modelagem
Para o desenvolvimento de hardware existem várias técnicas (níveis) de modelagem como: TL (Alto nível, serão necessários alguns refinamentos para chegar à sintese), RTL (baixo nível, facilmente sintetizável) e Estrutural (descrição de ligação entre módulos com sinais,bits...por aí) . A seguir explicaremos um pouco de cada dando uma idéia mais geral do que é cada etapa e como desenvolver em cada nível.
| Todos os arquivos utilizados neste artigo estão "hosteados" em code.google com exceção de um que é o myhdl.vpi que deverá ser compilado, mas isso só será necessário caso você deseje executar uma Co-Simulação. |
Sintaxe básica
Para introduzir os conceitos básicos de Python MyHDL falaremos um pouco da sintaxe. É bom salientar que em MyHDL não existem portas input e output. Pela lógica real, toda porta onde só se escreve é saída, de apenas leitura é entrada e de qualquer ação é entrada/saída. O código abaixo mostra a estrutura do código de um módulo chamado module. O decorator será o tipo de sensibilidade do processo onde temos:
- always_comb: sensível a qualquer entrada, também chamado de combinatorial;
- always: contém uma lista de sinais sensibilidade;
- instance: é o modo mais geral onde o desenvolvedor descreve dentro do módulo seu comportamento.
def modulo(entradas...,saidas...): @decorator def sub_processo(): ... return sub_processo @decorator def sub_processo2(): ... return sub_processo, sub_processo2
Existem os tipos primitivos de Python e outros de MyHDL, o desenvolvimento tem que respeitar um conjunto restritivo de dados para que o código criado possa ser convertido para Verilog. Abaixo mostra-se um resumo do uso do intbv (Integer Bit Vector) um dos dados mais comuns:
>>>from myhdl import intbv >>>a = intbv(0) # nesta caso o valor inicial de a será 0 e o tamanho do vetor é infinito e pode assumir qualquer valor. >>>b = intbv(1) # mesmo que acima,mas com o valor inicial igual 1 >>>c = intbv(0, min=-100, max=200) # neste caso estamos definindo o intervalo de existência do sinal, mas sem falar nada sobre o tamanho. >>>d = intbv(0, min=-8, max=7) # mesmo que a, mas com a definição do tamanho do vetor sendo igual a 3. O intervalo de existência neste caso é de -8 a 7 >>>e = d - 1 ; print hex(e) # e assume o valor resultado da operação que é -1 (lembre-se que python não tem frescura, sobrescreve mesmo!) -0x1L >>>e = intbv(d - 1)[3:]; print hex(e) 0x7L >>> >>>#Neste último comando, fazendo um cast para intbv()[3:] forçamos o resultado a ficar em "binário", gerando o complemento a dois do resultado. ... >>>
Muitos outros detalhes sobre intbv podem ser vistos na documentação de MyHDL [9]. Observação... a lógica binária de verdade só é alcançada quando trabalhamos com vetores de range definidos, ou seja, tem que ter o mínimo e o máximo no construtor do signal.
Temos os sinais que ligam módulos, submódulos, entre outros que são descritos pela classe Signal.
>>>from myhdl import Signal >>>a = Signal(0) #Nesta caso a é um Sinal que transmite inteiros de tamanho infinito que podem assumir qualquer valor. >>>b = Signal(0, delay=10) # o mesmo que o caso acima, porém o sinal tem um tempo de resposta de 10 alguma coisa (a unidade é abstraída). >>>c = Signal(intbv(0)[8:]) # Agora temos um que transmite vetores de bit com tamanho 8 >>>c.next = 3 #maneira com que se deve setar um valor naquele sinal. >>>print c #o valor de c está em c.val o próximo valor de c é c.next 0 >>>print c.next 3 >>>
RTL
É uma modelagem bem próxima da descrição real do hardware, é usado para síntese direta, ou seja, não é necessário mudar muita coisa para baixar no FPGA.
Lógica combinacional
Abaixo temos um exemplo simples de uma ULA que executa cálculos cada vez que alguma entrada muda.
def ULA( a, b, sel, result ): """ This module represents an ALU. It can execute severals operations. a,b - input value (8 bits signed interger) sel - selection (1 bits) { 0 - Additoin 1 - Subtraction result - output value (8 bits) """ @always_comb def process(): ###Addition if sel == 0x0: result.next = a + b ###Subtraction elif sel == 0x1: result.next = a - b else: raise Exception( "Error - Operation selected not implemented yet!" ) return process
Simulação e verificação
Para simular o comportamento da ula poderemos gerar alguns estímulos conforme segue abaixo:
def test_basic(): a, b, result = [Signal( intbv( 0, min=-256, max=256 ) ) for i in range( 3 )] sel = Signal( intbv( 0 )[1:] ) print "a b sel result" def test(): for test in range( 5 ): a.next, b.next, sel.next = randrange( 10 ), randrange( 10 ), randrange( 2 ) yield delay( 10 ) print "%-6s%-6s%-6s%-6s" % ( hex( a ), hex( b ), hex( sel ), hex( result ) ) testando = test() ula = ULA( a, b, sel, result ) sim = Simulation( ula, testando ) sim.run( 100 ) if __name__ == '__main__': test_basic()
O resultado gerado é:
a b sel result 0x6 0x2 0x1 0x4L 0x6 0x5 0x0 0xBL 0x3 0x3 0x1 0x0L 0x0 0x9 0x0 0x9L 0x2 0x3 0x1 0xFFL StopSimulation: No more events
Lógica seqüencial
Temos um registrador que executa ações quando um evento aconteceu com algum item da sua lista de sensibilidade.
def registrador8b( clk, inp, enable, clear, output ): """ Guarda vetores de oito bits clk:( 1 bit ) - clock do sistema inp: (vetor de bits tamanho 8) - entrada do registrador enable: (vetor de bits tamanho 8) - habilita o armazenamento da entrada clear: ( 1 bit ) - zera o registrador output: (vetor de bits tamanho 8) - saída, valor armazenado """ @always( clk.posedge ) def process(): if clear == HIGH: output.next = 0 else: if enable: output.next = inp return process
Simulação e verificação
Neste caso, é um pouco mais complicado gerar um teste básico para colocar o registrador para funcionar, porque precisamos de um gerador de clock, um gerador de estímulos e um monitor (para imprimir os valores dos sinais). Então, chegamos ao trecho de código abaixo:
def test_bench(): clk, enable, clear = [Signal( intbv( 0 )[1:] ) for i in range( 3 )] # apenas replicando 3x inp, output = Signal( intbv( 0 )[8:] ), Signal( intbv( 0 )[8:] ) reg = registrador8b( clk, inp, enable, clear, output ) HALF_PERIOD = delay( 10 ) @always( HALF_PERIOD ) def clk_gen(): clk.next = not clk @instance def stimgen(): for i in range( 1000 ): inp.next, enable.next, clear.next = randrange( 256 ), randrange( 2 ), randrange( 2 ) yield clk.negedge raise StopSimulation @instance def monitor(): print "input enable clear output" while 1: yield clk.posedge print "%-6s%-7s%-6s%-6s" % ( hex( inp ), enable, clear, hex( output ) ) return clk_gen, stimgen, reg, monitor
O resultado gerado é:
input enable clear output 0x8c 0 1 0x0L 0xe3 1 0 0x0L 0x69 0 1 0xe3 0x80 1 0 0x0L 0x2a 0 0 0x80 0x75 1 0 0x80 0x5b 0 1 0x75 0xab 1 1 0x0L 0xbc 0 0 0x0L 0x68 1 1 0x0L
Máquina de estados finita
Agora temos um módulo que representa o controle de um despertador. O controle é feito por uma máquina de estados finita a qual está descrita abaixo:
HIGH, LOW = 1, 0 t_State = enum( "ESPERANDO", "CONTANDO", "ARMAZENANDO", "TOCAR" ) def Despertador( clk, time, count , enable, state, reset, tocar ): """ Despertador controlado por uma máquina de estados finita. clk: (1 bit) - clock do sistema reset: (1 bit) - reset do sistema time: (32 bit) - tempo que será usador para tocar count: (32 bit) - valor atual do contador state: (estado) - estado atual do contador enable: (1 bit) - bit de habilitacao """ tmp = Signal( intbv( 0, min=0, max=10000 ) ) end_time = Signal( intbv( 0 ) ) @always( clk.posedge, reset.posedge ) def FSM(): if reset == HIGH: count.next = 0 tmp.next = 0 time.next = 0 state.next = t_State.ESPERANDO else: if state == t_State.ESPERANDO: tocar.next = 0 if enable: end_time.next = time state.next = t_State.CONTANDO elif state == t_State.CONTANDO: if tmp < 10: tmp.next = tmp + 1 state.next = t_State.CONTANDO else: tmp.next = 0 state.next = t_State.ARMAZENANDO elif state == t_State.ARMAZENANDO: if count < end_time: count.next = count + 1 state.next = t_State.CONTANDO else: count.next = 0 state.next = t_State.TOCAR elif state == t_State.TOCAR: tocar.next = 1 state.next = t_State.TOCAR else: raise ValueError( "Undefined state" ) return FSM
Simulação e verificação
A complexidade dos testes cresce com a complexidade do módulo. Abaixo temos um exemplo de uso do despertador. Observe na linha 39 o comando traceSignals, este gera um waveform da simulação possibilitando uma visualização mais detalhada do que acontece com os sinais.
def testbench(): clk, enable, state, reset, tocar = [Signal( bool( 0 ) ) for i in range( 5 )] time, count = Signal( intbv( 0 )[32:] ), Signal( intbv( 0 )[32:] ) state = Signal( t_State.ESPERANDO ) desp = Despertador( clk, time, count, enable, state, reset, tocar ) @always( delay( 10 ) ) def clkgen(): clk.next = not clk @instance def stimulus(): reset.next = HIGH yield clk.posedge reset.next = LOW enable.next = HIGH time.next = intbv( 10 )[32:] yield clk.posedge enable.next = LOW while not tocar: yield clk.posedge yield clk.posedge raise StopSimulation @instance def monitor(): print "count tocar" yield reset.negedge while 1: yield count, tocar.posedge print "%-6s%-6s" % ( hex( count ), tocar ) return clkgen, stimulus, monitor, desp tb_fsm = traceSignals( testbench ) #Gera um arquivo .vcd de saída que contém o "waveform" da simulação. #Este aquivo pode ser visualizado com GTKWave. sim = Simulation( tb_fsm ) sim.run()
Os resultados gerados pela simples impressão das saídas são:
count tocar 0x1L False 0x2L False 0x3L False 0x4L False 0x5L False 0x6L False 0x7L False 0x8L False 0x9L False 0xAL False 0x0 False 0x0 1
Podemos ver que neste caso foi gerado um arquivo .VCD que descreve o comportamento dos sinais durante a execução dos testes. Abaixo temos a visualização do arquivo .VCD gerado. Para visualizar o arquivo é preciso executar o comando:
>gtkwave -L <nome do arquivo>.vcd ...ou... >gtkwave -v <nome do arquivo>.vcd
Alto nível
A modelagem alto nível torna o desenvolvimento mais rápido e fácil, porém deixa o projeto menos sintetizável. Algumas formas de desenvolver em alto nível são encotradas na documentação de MyHDL, uma bem interessante é a descrição de uma memória simples cujo código está mostrado abaixo. Este nível de modelagem também dá suporte a uso de orientação a objetos e a tratamento de exceção.
memory = {} def sparseMemory( dout, din, addr, we, en, clk ): """ Sparse memory model based on a dictionary. Ports: dout -- data out din -- data in addr -- address bus we -- write enable: write if 1, read otherwise en -- interface enable: enabled if 1 clk -- clock input """ @always( clk.posedge ) def access(): if en: if we: memory[int( addr.val )] = din.val else: try: dout.next = memory[int ( addr.val )] except KeyError: raise Error, "Uninitialized address %s" % hex( addr ) return access
Simulação e verificação
No final da simulação tentamos acessar um endereço (0x15) de memória ainda não alocada, por isso a exceção é gerada.
= Memory ============== address content 0x1 0xFL 0x2 0x10L 0x3 0x11L 0x4 0x12L 0x5 0x13L 0x6 0x14L 0x7 0x15L 0x8 0x16L 0x9 0x17L 0xa 0x18L Traceback (most recent call last): File "/home/rjsp/workspace/artigo_myhdl/src/memory.py", line 102, in ? sim.run() File "/usr/lib/python2.4/site-packages/myhdl/_Simulation.py", line 132, in run waiter.next(waiters, actives, exc) File "/usr/lib/python2.4/site-packages/myhdl/_Waiter.py", line 140, in next clause = self.generator.next() File "/usr/lib/python2.4/site-packages/myhdl/_always.py", line 101, in genfunc func() File "/home/rjsp/workspace/artigo_myhdl/src/memory.py", line 46, in access raise Error, "Uninitialized address %s" % hex( addr ) __main__.Error: Uninitialized address 0x15L
Estrutural
Nesta seção, mostraremos a modelagem Estrutural a qual funciona baseada na idéia da construção de módulos usando módulos menores. Então abaixo vai um exemplo muito ilustrativo dessa idéia. Construiremos um shifter que desloca 8 bits juntando 4 que deslocam 2. Este exemplo é apenas ilustrativo, pois era muito mais fácil mudar o número de deslocamentos ao invés de montar tudo. Então:
def shifter2x( s_input, s_output ): """Desloca o vetor de entrada de 2 bits s_input: (vetor de bits tamanho 8) - entrada do shifter s_output: (vetor de bits tamanho 8) - saída, valor deslocado """ @always_comb def process(): s_output.next = intbv(s_input << 2)[32:] return process def shifter8x( s_input, s_output ): """Desloca o vetor de entrada de 8 bits s_input: (vetor de bits tamanho 8) - entrada do shifter s_output: (vetor de bits tamanho 8) - saída, valor deslocado """ s_12, s_23, s_34 = [Signal( intbv( 0 )[32:] ) for i in range( 3 )] instance_1 = shifter2x( s_input, s_12 ) instance_2 = shifter2x( s_12, s_23 ) instance_3 = shifter2x( s_23, s_34 ) instance_4 = shifter2x( s_34, s_output ) return instances()
Testes unitários
Um aspecto muito interessante de MyHDL é o suporte ao Unittest Framework nativo de Python, uma ferramenta muito poderosa. No desenvolvimento de hardware os testes são muito importantes, grande parte do tempo da construção de um dispositivo é dedicada apenas a testes. Muitos desta área gostam do termo Test-Driven Development que nada mais é que o desenvolvimento dos casos testes antes para depois implementar o código necessário para passar nesses testes. Abaixo temos um exemplo de teste unitário onde o módulo que está sendo testado foi citado anteriormento neste artigo. Então temos:
class shifters_unit_test( TestCase ): def shifter_general_test_case( self, bits, obj_shifter ): #recebe o número de bits a serem deslocados e um objeto shifter """Deslocando bits 1000 vezes""" def test( s_input, s_output ): for i in range( 10 ): r_input = randrange( 10 ) #gera um número aleatório entre 0 e 10 s_input.next = r_input yield delay( 10 ) expected = r_input << bits #desloca a entrada e armazena para compara com o resultado que vai sair do módulo actual = s_output self.assertEqual( actual, intbv( expected )[32:] ) for width in range( 100 ): s_input, s_output = [Signal( intbv( 0 )[32:] ) for i in range( 2 )] #cria os sinais de entrada e saída do módulo a ser testado. shifter = obj_shifter( s_input, s_output ) check = test( s_input, s_output ) sim = Simulation( shifter, check ) sim.run( quiet=1 ) def test_case_shifter2x( self ): """Deslocando 2 bits de números aleatórios 1000 vezes""" return self.shifter_general_test_case( 2, lambda a, b: shifter2x( a, b ) ) #dizemos aqui que o deslocamento é de 2 bits e o shifter é #do tipo shifter2x def test_case_shifter8x( self ): """Deslocando 8 bits de números aleatórios 1000 vezes""" return self.shifter_general_test_case( 8, lambda a, b: shifter8x( a, b ) ) #dizemos aqui que o deslocamento é de 8 bits e o shifter é #do tipo shifter8x if __name__ == '__main__': unittest.main()
Execução dos testes unitários
Quando os testes são executados as mensagens que abaixo aparecem:
> python shifters_unit_test.py -v Deslocando 2 bits de números aleatórios 1000 vezes ... ok Deslocando 8 bits de números aleatórios 1000 vezes ... ok ---------------------------------------------------------------------- Ran 2 tests in 2.970s OK >
Geração de código Verilog
A geração de código Verilog tem muitos poréns e porques.. A documentação é bem extensa, caso você leitor tenha algum interesse em aprofundar-se nesse tópico é bom dar uma olhada cuidadosa na documentação. Superficialmente, utilizaremos o registrador de 8 bit descrito anteriormente neste artigo para fazermos a conversão. Inicialmente é bem simples, apenas temos que definir os sinais e pronto. Então, segue abaixo o exemplo:
HIGH = 1 def registrador8b( clk, inp, enable, clear, output ): """ Guarda vetores de oito bits clk:( 1 bit ) - clock do sistema inp: (vetor de bits tamanho 8) - entrada do registrador enable: (vetor de bits tamanho 8) - habilita o armazenamento da entrada clear: ( 1 bit ) - zera o registrador output: (vetor de bits tamanho 8) - saída, valor armazenado """ @always( clk.posedge ) def process(): if clear == HIGH: output.next = intbv( 0 )[8:] else: if enable: output.next = inp return process if __name__ == '__main__': clk, enable, clear = [Signal( bool( 0 ) ) for i in range( 3 )] inp, output = [Signal( intbv( 0 )[8:] ) for i in range( 2 )] reg = toVerilog( registrador8b, clk, inp, enable, clear, output )
Resultados da conversão
Os resultados da conversão são o módulo em Verilog e uma interface que pode ser usada para Co-simulação (tópico que será explicado a seguir) e execução dos testes que ainda estão em python. O melhor de tudo é que você pode ter todos os testes ainda em Python sem precisar baixar o nível dos testes, ou seja, com os mesmos testcases pode-se testar o alto nível escrito em Python e o módulo baixo nível em Verilog. Com isso valida-se o código gerado, para então baixar em FPGA.
Módulo em Verilog
module registrador8b ( clk, inp, enable, clear, output ); input clk; input [7:0] inp; input enable; input clear; output [7:0] output; reg [7:0] output; always @(posedge clk) begin: _registrador8b_process if ((clear == 1)) begin output <= 8'h0; end else begin if (enable) begin output <= inp; end end end endmodule
Interface de Co-Simulação em Verilog
module tb_registrador8b; reg clk; reg [7:0] inp; reg enable; reg clear; wire [7:0] output; initial begin $from_myhdl( clk, inp, enable, clear ); $to_myhdl( output ); end registrador8b dut( clk, inp, enable, clear, output ); endmodule
Co-simulação Python/Verilog
A união entre Co-Simulação e Python UnitTests gera uma possibilidade muito interessante para o desenvolvimento de Hardware. Um dispositivo pode ser desenvolvido baseado em test driven development de sua origem (em MyHDL) até a hora da sintetização (código já em Verilog) usando a mesma suite de testes, todos feitos em Python. Isso facilita bastante, pois não é necessário baixar o nível dos testes, ou seja, todos os testes desenvolvidos são utilizados até o fim do projeto. As suites de teste podem ser bem complexas, menos custosas e assim "mais facilmente" podem capturar erros.
Funcionamento do mecanismo de Co-Simulação
A Co-Simulação tem como base a idéia da união entre a simulação de Python e de Verilog. Neste contexto, o interessante é que um módulo desenvolvido em MyHDL pode ser testado em Python, depois, o código Verilog gerado pode ser testado com os mesmos testes... Pois é, usando a Co-Simulação, parte do dispositivo pode ficar em MyHDL e parte em Verilog. Desta forma, a tradução do módulo em MyHDL para Verilog pode ser incremental. Traduz-se uma pequena parte por vez e executam-se os testes novamente, no fim, todo o dispositivo estará em Verilog e testado!
O suporte a co-simulação de MyHDL tem algumas restrições (melhor explicadas na documentação), onde as principais que eu identifiquei são:
- Apenas HDL passivo pode ser co-simulado, a parte que está rodando na máquina Python (MyHDL) tem que ser mestre do tempo, ou seja, o clock nunca poderá ser gerado no simulador Verilog por exemplo. Enfim, não se pode impôr restrições de tempo dentro do simulador Verilog (eu quando li isso fiquei aliviado, pensei que era o contrário... ainda bem que é assim!!!) Com isso a idéia de test driven development funciona mesmo! Testado e aprovado!
- Alguns sinais podem mudar do lado MyHDL entre a subida e a descida do clock, mas no lado Verilog as coisas só mudam ao fim do ciclo do clock.. Cuidado!
Pré-Co-Simulção
Para executar a Co-Simulação de MyHDL e Verilog é necessária a instalação de um Compilador e de um Simulador de Verilog, no meu caso eu usei o Icarus Verilog o qual já é os dois ao mesmo tempo (Compilador = iverilog e Simulador = vvp).
Para instalação do Icarus Verilog no Ubuntu:
> sudo apt-get install verilog ...espera e pronto!... >
Para a instalação no Gentoo:
> emerge =iverilog-0.8.4 ...espare um "pouquinho" mais e pronto... >
Falta apenas compilar uma coisinha... Dentro da pasta onde você descompactou a biblioteca MyHDL tem um arquivo que precisa ser compilado para ser usado na Co-Simulação. O arquivo que deve-se compilar é myhdl.c. Geralmente o caminho deste arquivo é: /myhdl-x.x.x/cosimulation/icarus/myhdl.c.
Para compilar e testar são necessários os seguintes comandos:
> cd /myhdl-x.x.x/cosimulation/icarus/ > make ...espare pouco, pronto!...para testar: > cd test > python test_all.py -v Check that only one bit changes in successive codewords ... ok Check that all codewords occur exactly once ... ok Check that the code is an original Gray code ... ok Check increment operation ... ok Check increment operation with suspended simulation runs ... ok dff test ... ok dff_clkout test ... ok dff test with simulation suspends ... ok dff_clkout test with simulation suspends ... ok ---------------------------------------------------------------------- Ran 9 tests in 7.617s OK >
Agora você deve copiar o arquivo myhld.vpi para a pasta do seu projeto. Pronto, rápido e fácil!
Co-Simulando
Para rodar a Co-Simulação temos que alterar alguns pontos no unittest do módulo em MyHDL. Vamos usar uma ula como exemplo ilustrativo. Abaixo está o código do unittest da ula que funciona para as duas versões, tanto MyHDL quanto do módulo já convertido para Verilog. Observe que na linha 36 e na linha 54 estamos fazendo uso do compilador iverilog e do simulador vvp o qual usa aquele arquivo compilado na seção anterior. Observe também a presença da flag cosim que indica o uso de co-simulação ou não.
def ula( a, b, sel, result ): """ This module represents an ALU. It can execute severals operations. a,b - input value (8 bits signed interger) sel - selection (1 bits) { 0 - Additoin 1 - Subtraction result - output value (8 bits) """ @always_comb def process(): #Addition if sel == 0x0: result.next = a + b #Subtraction elif sel == 0x1: result.next = a - b #this is necessary because the negative valeu must be two complement. else: raise Exception( "Error - Operation selected not implemented yet!" ) return process class ula_unit_test( TestCase ): def general_test_case( self, op, function, cosim=False ): """Caso de teste geral op: (bit) - bit de selação da ula [0x0, 0x1]; function: (objeto função nativo de pyhton) - É a função será executada pelo testcase. cosim: (bool) - uma flag que habilita a co-simulacao """ if cosim: cmd = "iverilog -o ula.o ./ula.v ./tb_ula.v" os.system( cmd ) def test( a, b, sel, result ): for i in range( 10 ): r_a, r_b = randrange( 128 ), randrange( 128 ) a.next, b.next = r_a, r_b sel.next = op yield delay( 10 ) expected = function( r_a, r_b ) actual = result self.assertEqual( actual.val , expected ) for width in range( 100 ): a, b, result = [Signal( intbv( 0, min=-256, max=256 ) ) for i in range( 3 )] sel = Signal( intbv( 0 )[1:] ) sim = None if cosim: cosim = Cosimulation( "vvp -m ./myhdl.vpi ula.o", a=a, b=b, sel=sel, result=result ) #para MyHDL isso é o mesmo que um ula onde os #sinais estão sendo ligados com os sinais #declarados acima. sim = Simulation( cosim, test( a=a, b=b, sel=sel, result=result ) ) else: sum = ula( a, b, sel, result ) check = test( a, b, sel, result ) sim = Simulation( sum, check ) sim.run( quiet=1 ) def test_case_addition( self ): """Checando 1000 vezes a soma entre inteiros de 8 bits""" return self.general_test_case( 0x0, lambda a, b: a + b ) def test_case_subtraction( self ): """Checando 1000 vezes a subtracao entre inteiros de 8 bits""" return self.general_test_case( 0x1, lambda a, b: a - b ) def test_case_addition_verilog_co_simulation( self ): """Checando 1000 vezes a soma entre inteiros de 8 bits - verilog""" return self.general_test_case( 0x0, lambda a, b: a + b, True ) def test_case_subtraction_verilog_co_simulation( self ): """Checando 1000 vezes a subtracao entre inteiros de 8 bits - verilog""" return self.general_test_case( 0x1, lambda a, b: a - b, True ) if __name__ == '__main__': unittest.main()
Resultado da Co-Simulação
Para Co-Simular basta executar:
>python ula_unit_test.py -v Checando 1000 vezes a soma entre inteiros de 8 bits ... ok Checando 1000 vezes a soma entre inteiros de 8 bits - verilog ... ok Checando 1000 vezes a subtracao entre inteiros de 8 bits ... ok Checando 1000 vezes a subtracao entre inteiros de 8 bits - verilog ... ok ---------------------------------------------------------------------- Ran 4 tests in 6.149s OK >
Pronto agora temos todo o processo em nossas mãos.
Conclusões
Depois de todo este artigo temos uma base interessante para começarmos a abstrair um pouco mais o desenvolvimento de hardware usando uma linguagem de alta produtividade como Python. Ao contrário de SystemC, MyHDL é produtivo e já tem nativo um tradutor de código para Verilog (uma possível feature do 0.6 é o toVHDL que gerará também código em VHDL). Um dos aspectos mais "inovadores" e interessantes deste projeto é a Co-Simulação a qual permite o uso de testes complexos (de rápido desenvolvimento -> baixo custo) que avaliam bem o comportamento do dispositivo. Abaixo temos um exemplo da aplicação de MyHDL onde quase todo o dispositivo já está rodando em Verilog, falta apenas traduzir e testar a memória.
Com MyHDL é possível desenvolver (prototipar) hardware das mais variadas classes (de muito simples a muito complexos) sem muito "custo", já que MyHDL é LGPL (não custa algumas dezenas de milhares de dólares) e a produtividade de Python é muito alta. Qualquer pessoa que pense em abrir uma empresa de desenvolvimento eficiente de hardware não precisa mais de um capital inicial de 500 mil reais para produzir. Então, aproveite!
Grato,
Rodrigo Peixoto.
Referências
- http://pt.wikipedia.org/wiki/Hardware
- http://pt.wikipedia.org/wiki/FPGA
- http://www.xilinx.com
- http://en.wikipedia.org/wiki/Hardware_description_language
- http://en.wikipedia.org/wiki/VHDL
- http://en.wikipedia.org/wiki/Verilog
- http://www.python.org
- http://myhdl.jandecaluwe.com/doku.php
- http://www.jandecaluwe.com/Tools/MyHDL/manual/
| Autor: Rodrigo Peixoto |
| Este artigo pode sofrer alterações. Fique ligado! |






