
Temos que dar o penúltimo passo no desenvolvimento prático do trabalho com o UDB. Hoje não desenvolveremos o uso do UDB Editor automatizado, mas no modo semi-manual, usando a ferramenta de configuração do Datapath. Uma ajuda muito boa para dominar essa ferramenta é AN82156 - PSoC 3, PSoC 4 e PSoC 5LP - Design de componentes do PSoC Creator com Datapaths UDB. Na verdade, eu mesmo estudei.
Talvez, ao ler nossas
traduções de documentação no UDB , alguém tenha tentado reproduzir o conhecimento de lá em prática e tenha notado que nem toda a funcionalidade descrita nas publicações está disponível no Editor do UDB. Isso se deve ao fato de que os desenvolvedores não começaram a colocar alguns mecanismos particularmente pesados no UDB Editor. Os autores do AN82156 argumentam que, através do UDB Editor, você não pode fazer o seguinte:
- organizar entrada e saída de dados paralelos;
- organizar gerenciamento FIFO dinâmico;
- implementar o inverso do sinal do relógio FIFO;
- implementar a função CRC;
- implementar a função PRS;
- implementar a escolha da transferência de entrada;
- implementar migração dinâmica de entrada.
De minha parte, acrescentarei que não encontrei como implementar a permutação de petiscos no Editor UDB.
Se essas funções forem necessárias no projeto, você precisará criar seu próprio código Verilog. Eu usei especificamente a palavra "criar" em vez de "escrever". Conhecer esta linguagem de programação é suficiente no nível de leitura. Quero dizer, você precisa entender qual design é necessário para o que. E poder escrever do zero é sempre útil, mas essa habilidade não é necessária para o que é apresentado neste artigo.
Como um problema solucionável, escolhi um estojo semi-sintético. Em geral, decidi enviar alguns dados para a porta paralela e, em particular, pelo que está à mão, o LCD de texto tem uma porta paralela. Tirei-o da impressora 3D MZ3D há três anos quando transplantei o último para o STM32. Portanto, o caso é semi-sintético: hoje, esses indicadores geralmente têm uma entrada I2C e não precisam se conectar através de uma pilha de fios na vida real. No entanto, os LCDs modernos também têm portas paralelas, para que todos possam usá-los para repetir o experimento.
Considere o esquema de troca de exibição retirado de reprap.org (isso não foi fácil, meu provedor bloqueia este site, além de vários outros técnicos, motivando-o com o fato de que ele vive no mesmo IP de alguém bloqueado).

Ótimo layout! Em primeiro lugar, não preciso pensar em ler: os dados no LCD só podem ser gravados (a linha R / W é aterrada e não está disponível no conector). Em segundo lugar, os dados vêm em um formato de 4 bits, o que significa que não apenas podemos calcular a saída paralela, mas também verificar a operação da função de permutação de mordidelas.
Criação de projeto
Então, inicie o PSoC Creator e selecione
Arquivo-> Novo-> Projeto :

Em seguida, escolho minha tábua de pão:

A seguir está o diagrama vazio:

Vou chamar o projeto
LCDTest2 :

Agora, como antes, vá para a guia
Componentes :

E, após selecionar o projeto, pressione o botão direito do mouse e selecione
Adicionar item do componente .

E aqui você tem que escolher o
Assistente de símbolo . Dê um nome ... Bem, digamos
LCD4bit .

Atribuí as seguintes portas ao símbolo:
clk é a entrada do relógio. Portas com um prefixo de LCD são portas LCD padrão.
Com fome - saídas informando à unidade DMA que existe espaço livre no FIFO, a idéia foi discutida em um artigo sobre como
controlar LEDs RGB . Clique em OK para obter o personagem.

Agora, com base neste símbolo, um modelo Verilog deve ser gerado. Clique com o botão direito do mouse nas proximidades do símbolo e selecione
Gerar Verilog no menu de contexto.

Temos o modelo mostrado na figura abaixo (no formato de texto ainda não faz sentido):

Criamos um módulo e algumas seções. Mas eles não criaram o Datapath ainda. Para adicioná-lo, vá para a árvore do projeto, selecione o arquivo
LCD4bit.v , pressione o botão direito do mouse e selecione a
Ferramenta de Configuração do
Datapath no menu de contexto que aparece:

Uma janela se abre diante de nós, que por enquanto mostrarei apenas parcialmente:

Por favor, ame e favor, editor do Datapath. Ele contém todos os bits que foram descritos na tradução da documentação proprietária. Mas há tantas dessas partes que, nos primeiros dias, eu olhei para ele, mas tinha medo de fazer qualquer coisa. Olha, olha e sai. E só depois de algum tempo, se acostumando, ele começou a tentar fazer alguma coisa. Na verdade, foi por isso que trouxe apenas parte da janela. Por que assustar todo mundo antes do tempo? Enquanto isso, precisamos apenas criar um Datapath, portanto, selecionamos o item de menu
Editar-> Novo Datapath :

Qual opção escolher na caixa de diálogo exibida?

A questão é um pouco mais séria do que parece. Deixe-me destacar o próximo parágrafo para que ninguém seja pego (eu fui pego, e depois vi perguntas na rede daquelas que recebi e ninguém realmente as respondeu, e a resposta está em
AN82156 , você só precisa lê-lo na diagonal, como diz lá frase curta e discreta).
Se você planeja trabalhar com dados paralelos, deve escolher definitivamente a opção CY_PSOC3_DP. Nenhuma outra opção conterá portas para conectar dados paralelos.
Então Deixe a instância ser chamada LCD_DP:

Clique em OK e feche a
ferramenta de configuração do
Datapath por enquanto , concordando em salvar o resultado. Voltaremos aqui mais tarde.
Nosso código Verilog foi expandido. Agora tem Datapath. Seu começo é completamente ilegível. Não é assustador, é configurado pela
ferramenta de configuração do
Datapath .

E governaremos o final da descrição do Datapath. Nosso site fica assim
(a partir deste ponto, faz sentido trazer tudo em forma de texto).)) LCD_DP( /* input */ .reset(1'b0), /* input */ .clk(1'b0), /* input [02:00] */ .cs_addr(3'b0), /* input */ .route_si(1'b0), /* input */ .route_ci(1'b0), /* input */ .f0_load(1'b0), /* input */ .f1_load(1'b0), /* input */ .d0_load(1'b0), /* input */ .d1_load(1'b0), /* output */ .ce0(), /* output */ .cl0(), /* output */ .z0(), /* output */ .ff0(), /* output */ .ce1(), /* output */ .cl1(), /* output */ .z1(), /* output */ .ff1(), /* output */ .ov_msb(), /* output */ .co_msb(), /* output */ .cmsb(), /* output */ .so(), /* output */ .f0_bus_stat(), /* output */ .f0_blk_stat(), /* output */ .f1_bus_stat(), /* output */ .f1_blk_stat(), /* input */ .ci(1'b0), // Carry in from previous stage /* output */ .co(), // Carry out to next stage /* input */ .sir(1'b0), // Shift in from right side /* output */ .sor(), // Shift out to right side /* input */ .sil(1'b0), // Shift in from left side /* output */ .sol(), // Shift out to left side /* input */ .msbi(1'b0), // MSB chain in /* output */ .msbo(), // MSB chain out /* input [01:00] */ .cei(2'b0), // Compare equal in from prev stage /* output [01:00] */ .ceo(), // Compare equal out to next stage /* input [01:00] */ .cli(2'b0), // Compare less than in from prv stage /* output [01:00] */ .clo(), // Compare less than out to next stage /* input [01:00] */ .zi(2'b0), // Zero detect in from previous stage /* output [01:00] */ .zo(), // Zero detect out to next stage /* input [01:00] */ .fi(2'b0), // 0xFF detect in from previous stage /* output [01:00] */ .fo(), // 0xFF detect out to next stage /* input [01:00] */ .capi(2'b0), // Software capture from previous stage /* output [01:00] */ .capo(), // Software capture to next stage /* input */ .cfbi(1'b0), // CRC Feedback in from previous stage /* output */ .cfbo(), // CRC Feedback out to next stage /* input [07:00] */ .pi(8'b0), // Parallel data port /* output [07:00] */ .po() // Parallel data port );
Assustador Agora vamos descobrir o que é isso - deixará de ser assustador. De fato, existem três grupos distintos neste texto. Vamos relembrar a tradução da documentação. Como era o caminho de dados na foto? Anotarei imediatamente na figura os lugares aos quais os grupos “1”, “2” e “3” pertencem.

Na verdade, o primeiro grupo de portas no código verilog são as entradas. Compare os nomes na saída do multiplexador de entrada (“1” na figura) e os nomes dos sinais no código.
Agora todas as entradas são zero. Teremos que conectar a entrada do relógio e podemos encaminhar até seis linhas de entrada, como foi feito no UDB Editor. Essas entradas são:
/* input */ .reset(1'b0), /* input */ .clk(1'b0), /* input [02:00] */ .cs_addr(3'b0), /* input */ .route_si(1'b0), /* input */ .route_ci(1'b0), /* input */ .f0_load(1'b0), /* input */ .f1_load(1'b0), /* input */ .d0_load(1'b0), /* input */ .d1_load(1'b0),
O segundo grupo são as saídas. Os nomes no código também coincidem com os nomes das entradas do multiplexador de saída "2":
/* output */ .ce0(), /* output */ .cl0(), /* output */ .z0(), /* output */ .ff0(), /* output */ .ce1(), /* output */ .cl1(), /* output */ .z1(), /* output */ .ff1(), /* output */ .ov_msb(), /* output */ .co_msb(), /* output */ .cmsb(), /* output */ .so(), /* output */ .f0_bus_stat(), /* output */ .f0_blk_stat(), /* output */ .f1_bus_stat(), /* output */ .f1_blk_stat(),
Somente uma espécie de Datapath especificada possui o terceiro grupo (as outras não, portanto, não há dados paralelos). Esses são sinais internos do Datapath através dos quais você pode encadear independentemente ou executar outras ações úteis. Os nomes no código também coincidem com os nomes dos sinais internos espalhados na figura. Nós, através de um deles (o último da lista, o nome dele é
po ), enviaremos dados paralelos diretamente para as pernas do chip.
/* input */ .ci(1'b0), // Carry in from previous stage /* output */ .co(), // Carry out to next stage /* input */ .sir(1'b0), // Shift in from right side /* output */ .sor(), // Shift out to right side /* input */ .sil(1'b0), // Shift in from left side /* output */ .sol(), // Shift out to left side /* input */ .msbi(1'b0), // MSB chain in /* output */ .msbo(), // MSB chain out /* input [01:00] */ .cei(2'b0), // Compare equal in from prev stage /* output [01:00] */ .ceo(), // Compare equal out to next stage /* input [01:00] */ .cli(2'b0), // Compare less than in from prv stage /* output [01:00] */ .clo(), // Compare less than out to next stage /* input [01:00] */ .zi(2'b0), // Zero detect in from previous stage /* output [01:00] */ .zo(), // Zero detect out to next stage /* input [01:00] */ .fi(2'b0), // 0xFF detect in from previous stage /* output [01:00] */ .fo(), // 0xFF detect out to next stage /* input [01:00] */ .capi(2'b0), // Software capture from previous stage /* output [01:00] */ .capo(), // Software capture to next stage /* input */ .cfbi(1'b0), // CRC Feedback in from previous stage /* output */ .cfbo(), // CRC Feedback out to next stage /* input [07:00] */ .pi(8'b0), // Parallel data port /* output [07:00] */ .po() // Parallel data port );
Então Enquanto trabalhamos, teremos que conectar algumas dessas entradas e saídas às nossas próprias entidades e o restante - apenas deixe-as na forma em que as criamos.
Usando o UDB Editor como referência
E agora temos um espaço em branco, sabemos onde e o que temos que escrever. Resta entender o que exatamente entraremos lá. Aconteceu que eu uso a linguagem Verilog nem todos os dias, então, em termos gerais, lembro de tudo, e escrever do zero para mim é sempre uma situação estressante. Quando o projeto já está em andamento, tudo é lembrado, mas se após alguns meses de inatividade eu começar algo do zero, é claro, não lembro mais os detalhes da sintaxe desse idioma em particular. Portanto, sugiro pedir ao ambiente de desenvolvimento que nos ajude.
O UDB Editor para auto-monitoramento cria código Verilog. Aproveitamos o fato de que os componentes que não estão envolvidos no circuito principal não são compilados, para que possamos criar um componente auxiliar no UDB Editor e ele não entrará no código de saída. Nós desenharemos um autômato lá, faremos um ajuste aproximado das entradas e saídas do Datapath e apenas transferiremos o texto gerado automaticamente para o nosso módulo verilog e modificaremos tudo de forma criativa. Isso é muito mais simples do que lembrar os detalhes da sintaxe da Verilog e escrever tudo do zero (embora quem use Verilog constantemente, é claro, será mais fácil escrever do zero: a conclusão criativa, como veremos em breve, é simples, mas exige hora).
Então, começamos a fazer um componente auxiliar. Com o movimento usual da mão, adicionamos um novo elemento ao projeto:

Este será um documento UDB, vamos chamá-lo de
UDBhelper :

É hora de pensar na máquina, que colocaremos na folha criada. Para fazer isso, precisamos considerar qual diagrama de tempo devemos formar com ele:


Então Primeiro, você precisa definir o sinal RS (já que o R / W é soldado a zero no hardware). Em seguida, aguarde tAS, aumente o sinal E e defina os dados (a configuração de dados em relação à margem positiva E não é limitada). Os dados devem estar no barramento não menos que tDSW, após o qual o sinal E. deve ser descartado, devendo permanecer no barramento por pelo menos tDHW e RS por pelo menos tAH.
RS é o comando ou sinalizador de dados. Se RS for zero, um comando será gravado; se for um, os dados serão gravados.
Sugiro o envio de comandos através do
FIFO0 e dados através do
FIFO1 . Na estrutura da tarefa atual, isso não contradiz nada. Então a máquina de estados finitos proposta por mim terá a seguinte forma:

No estado
ocioso , a máquina ainda não possui dados FIFO. Se os dados aparecerem em
FIFO0 , eles vão para
LoadF0 , onde no futuro receberão dados de
FIFO0 a A0.
Enquanto os comandos estão sendo transmitidos, os dados não devem ser enviados. Portanto, a condição para receber dados terá prioridade mais baixa do que a condição para receber comandos.

Os dados são recebidos em A1 no estado
LoadF1 (do
FIFO1, eles só podem ir para o registro A1 e não podem ir para o registro A0) e depois são copiados de A1 para A0 no estado
A1toA0 .
Qualquer que seja o caminho para o ponto de convergência das setas, temos dados em A0. Eles já são enviados para a porta paralela.
Empurramos E (no estado
E_UP1 ),
soltamos E (no estado
E_DOWN1 ). A seguir, teremos um estado para trocar petiscos (
SWAP ), após o qual E aumentará novamente (
E_UP2 ). Sobre isso, esgotei oito estados que podem ser codificados em três bits. E lembramos que a RAM de configuração dinâmica do Datapath possui apenas três entradas de endereço. Alguns truques podem ser aplicados, mas o artigo já é grande. Portanto, apenas na segunda vez, soltaremos E no estado
ocioso . Então oito estados são suficientes para nós.
Também colocamos o Datapath na planilha e atribuímos suas entradas e saídas de uma maneira que é familiar nos artigos anteriores. Aqui estão as entradas:

Aqui estão as saídas:

Nada de novo, tudo já foi descrito em artigos anteriores do ciclo. Então, temos um espaço em branco, com base no qual podemos fazer algo por conta própria. É verdade que, para garantir que tudo esteja funcionando, precisamos levar nosso sistema ao nível superior do projeto, caso contrário, nenhum erro será encontrado. E nas experiências iniciais sem erros não vai funcionar. Portanto, faremos mais uma ação auxiliar.
A descrição de como o circuito é feito vai além da descrição do trabalho com o UDB. Vou apenas mostrar qual circuito eu tenho. Existe apenas uma unidade DMA: ao enviar comandos para o LCD, é necessário suportar grandes pausas, por isso ainda é mais fácil fazer isso programaticamente. Para outras aplicações, você pode simplesmente colocar o segundo bloco DMA por analogia, usando o sinal
hungry0 .

Para atender com precisão o período de tempo, escolhi uma frequência de relógio igual a um megahertz. Seria possível ter uma frequência e mais alta, mas os dados são transmitidos por cabos longos em condições de alta interferência, portanto, é melhor reservar um tempo para definir os dados antes e depois do portão com uma margem. Se alguém repetir meus experimentos na mesma placa de ensaio - não use a porta P3.2: um capacitor é soldado a essa perna na placa. Eu matei por meia hora, até descobrir por que não formava um impulso E, que primeiro liguei lá. Joguei para a P3.1 - tudo funcionou imediatamente. Meu barramento de dados vai para P3.7-P3.4, RS para P3.3, então E originalmente foi para P3.2 ...
Bem aqui. Agora, se você tentar compilar o projeto, obteremos erros completamente previsíveis

Então o sistema está tentando coletar alguma coisa. Mas ela ainda não tem nada a cobrar. Prosseguimos para copiar o código. Para fazer isso, no Editor do UDB, alterne para a guia Verilog (essa guia está localizada sob a janela com a folha do Editor do UDB):

O que é familiar lá? No final do texto está o corpo do autômato. Vamos começar a migração a partir dele.
Coloque-o também em Datapath: /* ==================== State Machine: SM ==================== */ always @ (posedge clock) begin : Idle_state_logic case(SM) Idle : begin if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; end else if (( !F1empty ) == 1'b1) begin SM <= LoadF1 ; end end LoadF0 : begin if (( 1'b1 ) == 1'b1) begin SM <= E_Up1 ; end end E_Up1 : begin if (( 1'b1 ) == 1'b1) begin SM <= E_Down1 ; end end E_Down1 : begin if (( 1'b1 ) == 1'b1) begin SM <= SWAP ; end end SWAP : begin if (( 1'b1 ) == 1'b1) begin SM <= E_UP2 ; end end E_UP2 : begin if (( 1'b1 ) == 1'b1) begin SM <= Idle ; end end LoadF1 : begin if (( 1'b1 ) == 1'b1) begin SM <= A1toA0 ; end end A1toA0 : begin if (( 1'b1 ) == 1'b1) begin SM <= E_Up1 ; end end default : begin SM <= Idle; end endcase end
Existem declarações no topo desse código (nomes para estados, cadeias para Datapath, um registro que codifica o estado de um autômato). Nós os transferimos para o apropriado
seção do nosso código: /* ==================== Wire and Register Declarations ==================== */ localparam [2:0] Idle = 3'b000; localparam [2:0] LoadF0 = 3'b001; localparam [2:0] LoadF1 = 3'b010; localparam [2:0] E_Up1 = 3'b100; localparam [2:0] A1toA0 = 3'b011; localparam [2:0] E_Down1 = 3'b101; localparam [2:0] SWAP = 3'b110; localparam [2:0] E_UP2 = 3'b111; wire hungry0; wire F0empty; wire hungry1; wire F1empty; wire Datapath_1_d0_load; wire Datapath_1_d1_load; wire Datapath_1_f0_load; wire Datapath_1_f1_load; wire Datapath_1_route_si; wire Datapath_1_route_ci; wire [2:0] Datapath_1_select; reg [2:0] SM;
Bem, e
o local de ligação do sinal é transferível: /* ==================== Assignment of Combinatorial Variables ==================== */ assign Datapath_1_d0_load = (1'b0); assign Datapath_1_d1_load = (1'b0); assign Datapath_1_f0_load = (1'b0); assign Datapath_1_f1_load = (1'b0); assign Datapath_1_route_si = (1'b0); assign Datapath_1_route_ci = (1'b0); assign Datapath_1_select[0] = (SM[0]); assign Datapath_1_select[1] = (SM[1]); assign Datapath_1_select[2] = (SM[2]);
É hora de conectar o Datapath. O código portado do UDB Editor é bom para edição de máquina, mas não muito bom para edição manual. Lá, são criadas cadeias que se conectam às entradas do Datapath em uma extremidade e às constantes na outra. Mas no código criado pela
Ferramenta de Configuração do
Datapath (que faz tudo para o trabalho manual), todas as entradas já estão diretamente conectadas a zero constantes. Portanto, conectarei apenas as linhas que não são constantes, mas cortarei tudo do encaminhamento de constantes do texto transferido. A conexão ficou assim (a cor destaca os lugares que editei em relação aos criados automaticamente na Ferramenta de configuração do Datapath):

Mesmo texto: )) LCD_DP( /* input */ .reset(1'b0), /* input */ .clk(clk), /* input [02:00] */ .cs_addr(SM), /* input */ .route_si(1'b0), /* input */ .route_ci(1'b0), /* input */ .f0_load(1'b0), /* input */ .f1_load(1'b0), /* input */ .d0_load(1'b0), /* input */ .d1_load(1'b0), /* output */ .ce0(), /* output */ .cl0(), /* output */ .z0(), /* output */ .ff0(), /* output */ .ce1(), /* output */ .cl1(), /* output */ .z1(), /* output */ .ff1(), /* output */ .ov_msb(), /* output */ .co_msb(), /* output */ .cmsb(), /* output */ .so(), /* output */ .f0_bus_stat(hungry0), /* output */ .f0_blk_stat(F0empty), /* output */ .f1_bus_stat(hungry1), /* output */ .f1_blk_stat(F1empty),
Dados paralelos são um pouco mais complicados. O Datapath possui uma porta de oito bits e apenas quatro delas precisam ser trazidas para fora. Portanto, iniciamos o circuito auxiliar e conectamos apenas metade dele à saída:
wire [7:0] tempBus; assign LCD_D = tempBus[7:4];
E conecte-o assim:

Mesmo texto: /* input [07:00] */ .pi(8'b0), // Parallel data port /* output [07:00] */ .po( tempBus) // Parallel data port );
Tentamos montar (Shift + F6 ou através do item de menu
Build-> Generate Application ). Temos o erro:

Temos portas com
fome0 e com
fome1 (apareceram ao criar o componente), bem como cadeias com o mesmo nome (apareceram ao arrastar da amostra). Apenas remova essas correntes (deixando as portas). E em algum lugar o sinal do
relógio vazou, e nós temos esse circuito chamado
clk .
Após remover todos os circuitos desnecessários (aqueles que inicialmente lançavam zero constantes nas entradas do
Datapath , além de
hungry0 e
hungry1 ), obtemos o seguinte código para o início do nosso arquivo:
// Your code goes here /* ==================== Wire and Register Declarations ==================== */ localparam [2:0] Idle = 3'b000; localparam [2:0] LoadF0 = 3'b001; localparam [2:0] LoadF1 = 3'b010; localparam [2:0] E_Up1 = 3'b100; localparam [2:0] A1toA0 = 3'b011; localparam [2:0] E_Down1 = 3'b101; localparam [2:0] SWAP = 3'b110; localparam [2:0] E_UP2 = 3'b111; wire F0empty; wire F1empty; reg [2:0] SM; /* ==================== Assignment of Combinatorial Variables ==================== */ wire [7:0] tempBus; assign LCD_D = tempBus[7:4];
E ao substituir o
relógio por
clk no corpo da máquina, ao mesmo tempo, jogarei fora todas as linhas que são boas para a geração automática, mas com a edição manual, apenas crio confusão (todas as comparações que dão um resultado incondicional
TRUE e assim por diante). Em particular, no exemplo abaixo, você pode atravessar cerca de metade das linhas (e algumas
inicias / finais são opcionais, às vezes serão necessárias, porque adicionaremos ações, as destaquei):

Após pentear de acordo com o princípio acima (e substituir o
relógio por
clk ), esse corpo permanece
(ficou mais curto, o que significa que é mais fácil de ler): always @ (posedge clk) begin : Idle_state_logic case(SM) Idle : begin if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; end else if (( !F1empty ) == 1'b1) begin SM <= LoadF1 ; end end LoadF0 : begin SM <= E_Up1 ; end E_Up1 : begin SM <= E_Down1 ; end E_Down1 : begin SM <= SWAP ; end SWAP : begin SM <= E_UP2 ; end E_UP2 : begin SM <= Idle ; end LoadF1 : begin SM <= A1toA0 ; end A1toA0 : begin SM <= E_Up1 ; end default : begin SM <= Idle; end endcase end
Agora, durante a compilação, somos informados de que os
circuitos LCD_E e
LCD_RS não
estão conectados.
Na verdade, isso é verdade:

Chegou a hora de adicionar ação à máquina de estado. Substituiremos as declarações das portas correspondentes às cadeias não conectadas por
reg , pois as escreveremos no corpo da máquina (esta é a sintaxe da linguagem Verilog, se escrevermos, os dados deverão clicar, para isso precisamos de um gatilho, e isso é dado pela palavra-chave
reg ):

Mesmo texto: module LCD4bit ( output hungry0, output hungry1, output [3:0] LCD_D, output reg LCD_E, output reg LCD_RS, input clk );
E encha a máquina com ações. Eu já disse a lógica acima quando estava considerando o gráfico de transição do autômato, então mostrarei apenas o resultado:

Mesmo texto: always @ (posedge clk) begin : Idle_state_logic case(SM) Idle : begin LCD_E <= 0; if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; LCD_RS <= 0; end else if (( !F1empty ) == 1'b1) begin SM <= LoadF1 ; LCD_RS <= 1; end end LoadF0 : begin SM <= E_Up1 ; end E_Up1 : begin SM <= E_Down1 ; LCD_E <= 1'b1; end E_Down1 : begin SM <= SWAP ; LCD_E <= 1'b0; end SWAP : begin SM <= E_UP2 ; end E_UP2 : begin SM <= Idle ; LCD_E <= 1; end LoadF1 : begin SM <= A1toA0 ; end A1toA0 : begin SM <= E_Up1 ; end default : begin SM <= Idle; end endcase end
A partir deste momento, o projeto começa a se montar. Mas ele não vai funcionar ainda. Até agora, eu disse: "Nesse estado, carregaremos o registro do FIFO", "Nesse caso, A1 será copiado para A0", "Nibbles serão reorganizados". Em geral, falei muito, mas até agora não houve ações. Chegou a hora de cumpri-los. Observamos como os estados foram codificados:
localparam [2:0] Idle = 3'b000; localparam [2:0] LoadF0 = 3'b001; localparam [2:0] LoadF1 = 3'b010; localparam [2:0] E_Up1 = 3'b100; localparam [2:0] A1toA0 = 3'b011; localparam [2:0] E_Down1 = 3'b101; localparam [2:0] SWAP = 3'b110; localparam [2:0] E_UP2 = 3'b111;
Reabra a ferramenta de configuração do Datapath :

E comece a
editar as linhas
CFGRAM . Ao editar, lembre-se do esquema do Datapath, a saber:

Os quadros vermelhos na figura abaixo (e as setas na figura acima) destacaram as áreas corrigidas (e o caminho dos dados) para o estado
LoadF0 (código 001, ou seja,
Reg1 ). Também inseri comentários manualmente. O conteúdo de F0 deve entrar em A0.

Com quadros e setas verdes, marquei as configurações e o caminho para o estado LoadF1 (código 010 -
Reg2 ).
Com quadros e setas azuis, marquei as configurações e o caminho para o estado A1toA0 (código 011 -
Reg3 ).
Os quadros e setas roxos marcamos as configurações e o caminho para o estado do SWAP (código 110 -
Reg6 ).
Finalmente, as setas laranja mostram o caminho de dados paralelo. E nenhuma ação é tomada por eles. Eles sempre saem do
SRCA . Quase sempre temos A0 selecionado como
SRCA : os dados saem de A0. Portanto, para redirecionar os dados de entrada, teríamos que executar várias ações auxiliares, mas não aceitamos dados; portanto, aqui não precisamos dessas ações e todos encontrarão sua lista no
AN82156 . Também não precisamos editar nenhuma configuração estática do Datapath; portanto, feche a
Datapath Config Tool .
Só isso. Hardware concebido concluído. Começando a desenvolver código C. Para fazer isso, vá para a guia
Origem e edite o arquivo
main.c.
A inicialização regular do LCD e a saída de caracteres "ABC" são assim (lembro que os comandos vão para
FIFO0 , a documentação precisa inserir pausas entre as equipes e os dados vão para
FIFO1 , não encontrei nada sobre as pausas entre os dados):
volatile uint8_t* pFIFO0 = (uint8_t*) LCD4bit_1_LCD_DP__F0_REG; volatile uint8_t* pFIFO1 = (uint8_t*) LCD4bit_1_LCD_DP__F1_REG; pFIFO0[0] = 0x33; CyDelay (5); pFIFO0[0] = 0x33; CyDelay (100); pFIFO0[0] = 0x33; CyDelay (5); pFIFO0[0] = 0x20; CyDelay (5); pFIFO0[0] = 0x0C; // CyDelay (50); pFIFO0[0] = 0x01; // CyDelay (50); pFIFO1[0] = 'A'; pFIFO1[0] = 'B'; pFIFO1[0] = 'C';
O que é Por que há apenas o primeiro caractere na tela?

E se você adicionar atrasos entre a saída de dados - está tudo bem:

O osciloscópio não possui canais suficientes para esse trabalho. Verificamos o trabalho em um analisador lógico. O processo de gravação de dados é o seguinte.

Todos os dados estão no lugar (três pares de pacotes). O tempo para instalação e captura de dados é alocado em volume suficiente. Em geral, do ponto de vista dos diagramas de tempo - tudo é feito corretamente. O problema científico é resolvido, os diagramas de tempo desejados são formados. Aqui está a engenharia - não. A razão para isso é a lentidão do processador instalado no LCD. Entre bytes, adicione atrasos.
Iremos formar atrasos usando um contador de sete bits e, ao mesmo tempo, treinaremos para adicioná-lo a esse sistema. Vamos permanecer no estado de Espera não menos do que em um determinado período de tempo, e um contador de sete bits medirá esse tempo para nós. E, novamente, não escreveremos, mas criaremos código. Portanto, novamente vamos ao componente auxiliar do UDB Editor e adicionamos um contador à planilha, definindo seus parâmetros da seguinte maneira:

Este contador sempre funcionará (
Ativar está definido como 1). Mas ele será carregado quando a máquina estiver no estado
E_UP2 (após o qual caímos imediatamente no estado
ocioso ). A linha
Count7_1_tc será aumentada para 1 quando o contador contar para zero, o que
criaremos uma condição adicional para sair do estado de
espera . A figura também contém o valor do período, mas não o encontraremos no código Verilog. Terá que ser inserido no código C. Mas primeiro, transferimos o código Verilog gerado automaticamente, alternando para a guia Verilog. Primeiro de tudo, o contador deve estar conectado (vemos esse código no início do arquivo e o movemos também para o início):
`define CY_BLK_DIR "$CYPRESS_DIR\..\psoc\content\CyComponentLibrary\CyComponentLibrary.cylib\Count7_v1_0" `include "$CYPRESS_DIR\..\psoc\content\CyComponentLibrary\CyComponentLibrary.cylib\Count7_v1_0\Count7_v1_0.v"
Como o refinamento criativo de linhas e constantes é realizado já foi descrito, então mostrarei o resultado. Aqui estão as cadeias e atribuições adicionadas como resultado (o resto define as constantes, então eu as joguei fora):
wire Count7_1_load; wire Count7_1_tc; assign Count7_1_load = (SM==E_UP2);
E aqui está o próprio contador, colocado no final do arquivo. Todas as constantes são atribuídas às portas diretamente nesta declaração:
Count7_v1_0 Count7_1 ( .en(1'b1), .load(Count7_1_load), .clock(clk), .reset(1'b0), .cnt(), .tc(Count7_1_tc)); defparam Count7_1.EnableSignal = 1; defparam Count7_1.LoadSignal = 1;
Para permitir que esse contador funcione, adicionamos automaticamente uma condição adicional para sair do estado de
espera :

Mesmo texto: case(SM) Idle : begin LCD_E <= 0; if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; LCD_RS <= 0; end else if (( !F1empty &Count7_1_tc ) == 1'b1) begin SM <= LoadF1 ; LCD_RS <= 1; end end
A API para o contador adicionado dessa maneira não é criada; portanto, adicionamos duas linhas mágicas à função
principal , que formei na imagem e semelhança do que vi na API de projetos anteriores (a primeira linha define o valor carregado da conta, a mesma carga, a segunda inicia o contador):
*((uint8_t*)LCD4bit_1_Count7_1_Counter7__PERIOD_REG) = 0x20; *((uint8_t*)LCD4bit_1_Count7_1_Counter7__CONTROL_AUX_CTL_REG) |= 0x20; // Start
O analisador mostra que, no caso modificado, o atraso é óbvio:

O LCD também possui todos os três caracteres.
Mas a produção de caracteres programáticos na vida real é inaceitável. Apenas adicioná-los ao FIFO transbordará. Aguarde o FIFO esvaziar - isso significa criar grandes atrasos para o núcleo do processador.
O processador opera a uma frequência de 72 MHz e os dados são emitidos por 7-8 ciclos de clock a uma frequência de 1 MHz. Portanto, na vida real, o texto deve ser exibido usando o DMA. É aqui que o princípio "Iniciar e esquecer" é útil. Todos os atrasos no diagrama de temporização serão gerados pelo UDB, e o controlador DMA determinará a disponibilidade do FIFO para receber dados para nós. O núcleo do processador precisa apenas formar uma linha na memória e configurar o DMA, após o que ele pode executar outras tarefas sem se preocupar com a saída do LCD.Adicione o seguinte código: static const char line[] = "This is a line"; /* Defines for DMA_D */ #define DMA_D_BYTES_PER_BURST 1 #define DMA_D_REQUEST_PER_BURST 1 /* Variable declarations for DMA_D */ /* Move these variable declarations to the top of the function */ uint8 DMA_D_Chan; uint8 DMA_D_TD[1]; /* DMA Configuration for DMA_D */ DMA_D_Chan = DMA_D_DmaInitialize(DMA_D_BYTES_PER_BURST, DMA_D_REQUEST_PER_BURST, HI16(line), HI16(LCD4bit_1_LCD_DP__F1_REG)); DMA_D_TD[0] = CyDmaTdAllocate(); CyDmaTdSetConfiguration(DMA_D_TD[0], sizeof(line)-1, CY_DMA_DISABLE_TD, CY_DMA_TD_INC_SRC_ADR); CyDmaTdSetAddress(DMA_D_TD[0], LO16((uint32)line), LO16((uint32)LCD4bit_1_LCD_DP__F1_REG)); CyDmaChSetInitialTd(DMA_D_Chan, DMA_D_TD[0]); CyDmaChEnable(DMA_D_Chan, 1);
Na tela, temos:
Conclusão
, , UDB — Datapath Config Tool. , UDB Editor, UDB, , UDB Editor. , , , UDB Editor.
.