Formulação do problema
Você foi persuadido a projetar uma máquina de refrigerantes para o escritório. As bebidas são parcialmente cobertas pelo sindicato, portanto custam apenas 5 rublos. A máquina aceita moedas de 1, 2 e 5 rublos. Assim que o cliente pagar a quantia necessária, a máquina distribuirá a bebida e retornará a mudança. Projete uma máquina de estado para uma máquina de refrigerantes. As entradas da máquina são de 1, 2 e 5 rublos, a saber, qual dessas moedas está inserida.
Suponha que apenas uma moeda seja inserida por relógio. A máquina tem saídas: despeje refrigerante, retorne 1 rublo, retorne 2 rublos, retorne 2 por 2 rublos. Assim que 5 rublos (ou mais) são coletados na máquina, ele define o sinal "Pour GASING", bem como os sinais retornando a alteração correspondente. A máquina deve estar pronta para aceitar moedas novamente.
Teoria
Máquinas de estado finito ou máquina de estado finito (FSM) pertencem à classe de circuitos seriais síncronos, que representam a grande maioria dos circuitos de circuitos digitais. É assim que você deve implementar seus projetos (pelo menos no início). Este método fornece repetibilidade e verificação do circuito e é independente das relações de atraso dos vários elementos do circuito. As regras para a construção de circuitos sequenciais síncronos declaram que um circuito é um circuito sequencial síncrono se seus elementos atenderem às seguintes condições:
- Cada elemento do circuito é um registro ou um circuito combinacional.
- Pelo menos um elemento do circuito é um registrador.
- Todos os registros são cronometrados com um único sinal de relógio.
- Cada caminho cíclico contém pelo menos um registro.
Uma máquina de estados possui vários estados, que são armazenados em registradores. Quando um sinal de relógio chega, a máquina de estado pode mudar seu estado, e como exatamente o estado mudará depende dos sinais de entrada e do estado atual. No caso mais simples, pode não haver nenhum sinal de entrada, portanto o divisor de frequência funciona. Existem duas classes principais de máquinas de estados finitos: um autômato Moore, no qual os sinais de saída dependem apenas do estado atual do autômato, e um autômato Mealy, no qual os sinais de saída dependem do estado atual e dos sinais de entrada. Em princípio, qualquer máquina de estados finitos pode ser implementada de acordo com o esquema de Moore e o esquema de Miley, a diferença entre eles será que o autômato Moore terá mais estados e haverá um relógio atrás do autômato Mily.Para o circuito da máquina de refrigerante, usarei o circuito de Miles. Vamos declarar a máquina de estado:
Símbolo | Descrição |
---|---|
S 0 | O estado inicial, a quantidade acumulada de 0 rublos. |
S 1 | A quantidade acumulada é de 1 esfrega. |
S 2 | Acumulado 2 rublos. |
S 3 | Acumulado 3 rublos. |
S 4 | Acumulado 4 rublos. |
O sinal de entrada será um barramento de dois bits, com a seguinte codificação da denominação da moeda:
Símbolo | Valor | Descrição |
---|---|---|
Eu 1 | 01 | 1 RUB |
I 2 | dez | RUB 2 |
I 5 | onze | 5 esfregar |
Vamos desenhar um diagrama de estados do nosso autômato (nos diagramas de estados do autômato Mealy, é necessário indicar os sinais de saída nas setas de transição de estado, não o farei para não confundir o diagrama, todos os sinais de saída serão descritos na tabela abaixo):
Vamos escrever a tabela de mudanças de estados e sinais de saída:
Estados | Sinais de entrada | |||||
---|---|---|---|---|---|---|
S | S' | insert | pour_water | C 1 . change1 | 2 . change2 | 2 2 . change22 |
S0 | S1 | I1 | 0 | 0 | 0 | 0 |
S0 | S2 | I2 | 0 | 0 | 0 | 0 |
S0 | S0 | I5 | 1 | 0 | 0 | 0 |
S1 | S2 | I1 | 0 | 0 | 0 | 0 |
S1 | S3 | I2 | 0 | 0 | 0 | 0 |
S1 | S0 | I5 | 1 | 1 | 0 | 0 |
S2 | S3 | I1 | 0 | 0 | 0 | 0 |
S2 | S4 | I2 | 0 | 0 | 0 | 0 |
S2 | S0 | I5 | 1 | 0 | 1 | 0 |
S3 | S4 | I1 | 0 | 0 | 0 | 0 |
S3 | S0 | I2 | 1 | 0 | 0 | 0 |
S3 | S0 | I5 | 1 | 1 | 1 | 0 |
S4 | S0 | I1 | 1 | 0 | 0 | 0 |
S4 | S0 | I2 | 1 | 1 | 0 | 0 |
S4 | S0 | I5 | 1 | 0 | 0 | 1 |
Quartus Prime
O Quartus possui uma edição Lite gratuita, que possui algumas limitações em relação à edição profissional; a principal limitação é não mais que 10.000 linhas de código-fonte para simular um projeto. Você pode baixá-lo, após o registro, pelo link , no momento em que este artigo foi escrito, a versão mais recente era 19.1, baseada no trabalho com esta versão, escrevi um artigo. Escolhemos a Lite Edition, versão 19.1, sistema operacional Windows (note que existe uma versão do Quartus para Linux e funciona bem, surgem problemas com o ModelSim, que é de 32 bits e usa uma versão antiga da biblioteca de exibição de fontes, portanto, primeiro recomendo usar a versão do Windows ), selecione a guia Arquivos combinados. O tamanho do arquivo para o download é muito grande - 5,6 Gb, lembre-se disso. Expanda o arquivo baixado e executesetup.bat . A instalação ocorre de maneira padrão; usamos a seleção padrão de componentes.
Criação de projeto
Para criar um novo projeto, selecione o Assistente para Arquivo -> o Novo ... do Projeto . A primeira janela do Assistente é informativa, clique em Avançar , na segunda janela, selecione onde o projeto estará localizado, seu nome "soda_machine" e o elemento de design de nível superior "soda_machine" , como na figura:
Na próxima janela, selecione "Projeto vazio" . A janela para adicionar arquivos "Adicionar arquivos" , não adicione nada. A janela para selecionar o dispositivo “Family, Devices & Board Settings” é muito importante para um projeto real, mas como nosso projeto está longe de ser real, deixamos as configurações padrão aqui, como na figura:
Janela para selecionar configurações para outras ferramentas"Configurações da ferramenta EDA" , selecione para simular o projeto para usar o formato "ModelSim-Altera" e "System Verilog HDL" como na figura:
Na última janela de informações "Resumo" , clique em Concluir .
Escrevendo código fonte
Teremos dois arquivos principais com o código-fonte, este é o próprio módulo soda_machine e sua bancada de testes. Ambos os arquivos usarão o tipo de dados insert_type , que descreve como codificamos as denominações das moedas e é lógico separá-lo em um arquivo separado. Mas existem algumas dificuldades associadas aos recursos de compilação do Quartus e ModelSim. O Quartus compila todos os arquivos de origem em uma única passagem e o ModelSim compila cada arquivo separadamente, para que, ao compilar o Quartus, não haja redefinição do tipo insert_type , usei a técnica do C / C ++ include guard com base nas diretivas do microprocessador. Além disso, esse ModelSim garantiria que o tipo insert_type usado no módulo soda_machinee no banco de testes, o mesmo, colocou sua descrição dentro do pacote soda_machine_types . Com esses requisitos em mente, o arquivo soda_machine_types.sv se parece com o seguinte:
soda_machine_types.sv
`ifndef soda_machine_types_sv_quard
package soda_machine_types;
typedef enum logic [1:0] {I1=2'b01, I2=2'b10, I5=2'b11} insert_type;
endpackage
`define soda_machine_types_sv_quard
`endif
Agora, o próprio módulo soda_machine está localizado no arquivo soda_machine.sv :
soda_machine.sv
`include "soda_machine_types.sv"
import soda_machine_types::*;
module soda_machine(
input logic clk, // Clock
input logic reset, // Active high level
input insert_type insert,
output logic pour_water,
output logic change1,
output logic change2,
output logic change22);
typedef enum logic [2:0] {S0, S1, S2, S3, S4} state_type;
(* syn_encoding = "default" *) state_type state, nextstate;
//
always_ff @(posedge clk, posedge reset)
if (reset)
state <= S0;
else
state <= nextstate;
//
always_comb
case (state)
S0:
case (insert)
I1:
nextstate = S1;
I2:
nextstate = S2;
I5:
nextstate = S0;
endcase
S1:
case (insert)
I1:
nextstate = S2;
I2:
nextstate = S3;
I5:
nextstate = S0;
endcase
S2:
case (insert)
I1:
nextstate = S3;
I2:
nextstate = S4;
I5:
nextstate = S0;
endcase
S3:
if (insert == I1)
nextstate = S4;
else
nextstate = S0;
S4:
nextstate = S0;
endcase
//
assign pour_water = (state == S4) | (insert == I5) | (state == S3) & (insert == I2);
assign change1 = (state == S1) & (insert == I5) | (state == S3) & (insert == I5) | (state == S4) & (insert == I2);
assign change2 = (state == S2) & (insert == I5) | (state == S3) & (insert == I5);
assign change22 = (state == S4) & (insert == I5);
endmodule
Como o estado da máquina de estado será codificado, eu deixei para o Quartus. Para indicar como a codificação deve ser feita, o atributo (* syn_encoding = "default" *) é usado , outras opções de codificação podem ser vistas aqui .
Deve-se notar que em um projeto real os sinais de saída da lógica combinacional da máquina Miles devem ser armazenados em registradores e alimentados da saída dos registradores para a saída FPGA. Os sinais de entrada devem ser sincronizados com a frequência do relógio usando sincronizadores para evitar cair em um estado metaestável.
Para adicionar arquivos ao projeto, use Arquivo -> Novo "Arquivo SystemVerilog HDL"e dê um nome apropriado ao salvar. Após adicionar esses dois arquivos, o projeto pode ser compilado com Processando -> Iniciar Compilação . Após a compilação bem-sucedida, você pode ver o esquema resultante Ferramentas -> Netlist Viewers -> RTL Viewer :
RTL Viewer
Para exibir o statechart da máquina de estado Ferramentas -> Visualizadores de Netlist -> Visualizador de Máquina de Estado
State Machine Viewer
Na guia Codificação, você pode ver que o Quartus aplicou o esquema de codificação "one-hot"; é quando um D-flip-flop separado é usado para cada estado, e o estado S 0 é codificado como 0, e não 1 como em outros estados, isso é feito para simplificar o circuito de redefinição para o inicial. Estado. Você pode perceber que o RTL Viewer não mostra exatamente um diagrama esquemático, mas um conceito. Para visualizar o diagrama esquemático, use Ferramentas -> Netlist Viewrs -> Technology Map Viewer (Pós-ajuste)
Simulação
Em princípio, no momento, temos um diagrama de uma máquina de venda automática de água com gás, mas precisamos garantir que ela funcione corretamente; para isso, escreveremos uma bancada de testes e a colocaremos no arquivo soda_machine_tb.sv :
soda_machine_tb.sv
`include "soda_machine_types.sv"
import soda_machine_types::*;
module soda_machine_tb;
insert_type insert;
logic [5:0] testvectors[10000:0];
int vectornum, errors;
logic clk, reset, pour_water, change1, change2, change22;
logic pour_water_expected, change1_expected, change2_expected, change22_expected;
//
soda_machine dut(
.clk(clk),
.reset(reset),
.insert(insert),
.pour_water(pour_water),
.change1(change1),
.change2(change2),
.change22(change22)
);
//
always
#5 clk = ~clk;
//
initial begin
//
$readmemb("../../soda_machine.tv", testvectors);
vectornum = 0;
errors = 0;
clk = 1;
//
reset = 1; #13; reset = 0;
end
//
always @(posedge clk) begin
#1; {insert, pour_water_expected, change1_expected, change2_expected, change22_expected} = testvectors[vectornum];
end
// ,
always @(negedge clk)
if (~reset) begin
if ((pour_water !== pour_water_expected) || (change1 !== change1_expected) || (change2 !== change2_expected) ||
(change22 !== change22_expected)) begin
$error("%3d test insert=%b\noutputs pour_water=%b (%b expected), change1=%b (%b expected), change2=%b (%b expected), change22=%b (%b expected)",
vectornum + 1, insert, pour_water, pour_water_expected, change1, change1_expected, change2, change2_expected, change22, change22_expected);
errors = errors + 1;
end
vectornum = vectornum + 1;
if (testvectors[vectornum] === 6'bx) begin
$display("Result: %3d tests completed with %3d errors", vectornum, errors);
$stop;
end
end
endmodule
Para testar nosso módulo, o arquivo de vetores de teste soda_machine.tv é usado :
soda_machine.tv
01_0_0_0_0
01_0_0_0_0
01_0_0_0_0
01_0_0_0_0
01_1_0_0_0
10_0_0_0_0
10_0_0_0_0
10_1_1_0_0
11_1_0_0_0
10_0_0_0_0
10_0_0_0_0
11_1_0_0_1
10_0_0_0_0
11_1_0_1_0
01_0_0_0_0
01_0_0_0_0
01_0_0_0_0
11_1_1_1_0
Os dois primeiros bits são a entrada de inserção, os próximos 4 bits são a nossa espera pelas saídas: pour_water, change1, change2, change22. Por exemplo, no início do arquivo, uma moeda de rublo é inserida 5 vezes seguidas; na quinta moeda, esperamos o sinal de pour_water aparecer, enquanto os sinais de alteração estão inativos. O arquivo soda_machine.tv é adicionado ao projeto Arquivo -> Novo "Arquivo de Texto" .Para
a conveniência de trabalhar com o ModelSim, adicione o arquivo soda_machine_run_simulation.do com o seguinte conteúdo:
soda_machine_run_simulation.do
add wave /soda_machine_tb/dut/clk
add wave /soda_machine_tb/dut/reset
add wave /soda_machine_tb/dut/insert
add wave /soda_machine_tb/dut/state
add wave /soda_machine_tb/dut/nextstate
add wave /soda_machine_tb/dut/pour_water
add wave /soda_machine_tb/dut/change1
add wave /soda_machine_tb/dut/change2
add wave /soda_machine_tb/dut/change22
view structure
view signals
run -all
wave zoom full
Ele executará nossa simulação e exibirá as formas de onda no ModelSim. O arquivo soda_machine_run_simulation.do é adicionado ao projeto File -> New "Tcl script File"
Agora vamos configurar o projeto para que a simulação inicie automaticamente. Selecione o item de menu Atribuições -> Configurações , selecione a categoria Configurações da ferramenta EDA -> Simulação . Nas configurações do NativeLink, selecione Compilar bancada de testes: e clique no botão Bancos de teste ... na janela Bancas de teste que é aberta, clique no botão Novo ... Na janela Novas configurações da bancada de testes que é aberta, preencha o campo Nome da bancada de testes: soda_machine_tbe clique no botão de seleção de arquivo ... na parte inferior da janela, selecione nosso arquivo soda_machine_tb.sv e clique no botão Adicionar . Ele deve aparecer como na figura:
Na janela Novas Configurações do Banco de Testes , clique em OK . A janela Bancadas de teste deve ter a seguinte aparência:
Na janela Bancadas de teste , clique em OK . Nas configurações do NativeLink, defina a caixa de seleção Usar script para configurar a simulação e selecione o arquivo soda_machine_run_simulation.do . A janela Configurações
deve ficar assim:
Na janela Configurações , clique emOK , compilamos o projeto Processing -> Start Compilation , iniciamos a simulação Tools -> Run Simulation Tool -> RTL Simulation . O ModelSim deve iniciar e o projeto simulará. Aparência da guia Transcrição:
Guia Transcrição ModelSim
O quadro vermelho marca a saída de nossa bancada de testes sobre o número de testes realizados e os erros encontrados. Aparência da guia Wave:
Guia ModelSim Wave
Código fonte do projeto
O código fonte do projeto está em github.com/igoral5/soda_machine Clone o projeto e abra o projeto com Quartus File -> Open Project ...
selecione o arquivo soda_machine.qpf . Em seguida, compile o projeto Processing -> Start Compilation e inicie a simulação Tools -> Run Simulation Tool -> RTL Simulation .