Usar la herramienta de configuración de Datapath



Tenemos que dar el penúltimo paso en el desarrollo práctico de trabajar con UDB. Hoy no desarrollaremos usando el editor UDB automatizado, sino en modo semi-manual usando la herramienta de configuración de Datapath. Una muy buena ayuda para dominar esta herramienta es AN82156 - PSoC 3, PSoC 4 y PSoC 5LP - Diseño de componentes de PSoC Creator con UDB Datapaths. En realidad, lo estudié yo mismo.

Quizás, mientras leía nuestras traducciones de documentación sobre UDB , alguien intentó reproducir el conocimiento desde allí en la práctica y notó que no toda la funcionalidad descrita en las publicaciones está disponible en el Editor de UDB. Esto se debe al hecho de que los desarrolladores no comenzaron a colocar algunos mecanismos particularmente engorrosos en el Editor UDB. Los autores de AN82156 argumentan que a través del Editor UDB no puede hacer lo siguiente:

  • organizar la entrada y salida de datos paralelos;
  • organizar la gestión dinámica de FIFO;
  • implementar el inverso de la señal de reloj FIFO;
  • implementar la función CRC;
  • implementar la función PRS;
  • implementar la elección de la transferencia entrante;
  • Implementar la migración entrante dinámica.

Por mi parte, agregaré que no encontré cómo implementar la permutación de nibbles en el Editor UDB.

Si se necesitan estas funciones en el proyecto, deberá crear su propio código Verilog. Usé específicamente la palabra "crear" en lugar de "escribir". Conocer este lenguaje de programación es suficiente a nivel de lectura. Quiero decir, debes entender qué diseño se necesita para qué. Y poder escribir desde cero siempre es útil, pero esta habilidad no es necesaria para lo que se presenta en este artículo.

Como problema solucionable, elegí un estuche semisintético. En general, decidí enviar algunos datos al puerto paralelo y, en particular, de lo que está a la mano, el LCD de texto tiene un puerto paralelo. Lo saqué de la impresora 3D MZ3D hace tres años cuando trasplanté esta última a STM32. Por lo tanto, el caso es semisintético: hoy en día, tales indicadores generalmente tienen una entrada I2C y no necesitan conectarse a través de una pila de cables en la vida real. Sin embargo, los LCD modernos también tienen puertos paralelos, por lo que todos pueden usarlos para repetir el experimento.

Considere el esquema de cambio de pantalla tomado de reprap.org (esto no fue fácil, mi proveedor bloquea este sitio, así como también otros técnicos, motivándolo con el hecho de que vive en la misma IP que alguien bloqueado).



Gran diseño! En primer lugar, no tengo que pensar en leer: los datos en la pantalla LCD solo se pueden escribir (la línea R / W está conectada a tierra y no está disponible en el conector). En segundo lugar, los datos vienen en un formato de 4 bits, lo que significa que no solo podemos calcular la salida paralela, sino también verificar el funcionamiento de la función de permutación de mordisco.

Creación de proyectos


Entonces, inicie PSoC Creator y seleccione Archivo-> Nuevo-> Proyecto :



A continuación, elijo mi placa de pruebas:



El siguiente es el diagrama vacío:



Llamaré al proyecto LCDTest2 :



Ahora, como antes, vaya a la pestaña Componentes :



Y, una vez seleccionado el proyecto, presione el botón derecho del mouse y luego seleccione Agregar elemento componente .



Y aquí tienes que elegir el Asistente de símbolos . Dale un nombre ... Bueno, digamos LCD4bit .



Asigné los siguientes puertos al símbolo:



clk es la entrada del reloj. Los puertos con un prefijo LCD son puertos LCD estándar. hambre : salidas que le dicen a la unidad DMA que hay espacio libre en FIFO, la idea se discutió en un artículo sobre el control de LED RGB . Haga clic en Aceptar para obtener el personaje.



Ahora, basado en este símbolo, se debe generar una plantilla Verilog. Haga clic con el botón derecho del mouse cerca del símbolo y seleccione Generar Verilog en el menú contextual.



Obtuvimos la plantilla que se muestra en la figura a continuación (en forma de texto todavía no tiene sentido):



Hemos creado un módulo y algunas secciones. Pero todavía no han creado Datapath. Para agregarlo, vaya al árbol del proyecto, seleccione el archivo LCD4bit.v , presione el botón derecho del mouse y seleccione la herramienta de configuración de Datapath en el menú contextual que aparece:



Se abre una ventana ante nosotros, que por ahora solo mostraré parcialmente:



Por favor, amor y favor, editor de Datapath. Contiene todos los bits que se describieron en la traducción de la documentación patentada. Pero hay tantos de estos bits que en los primeros días lo miré, pero tenía miedo de hacer algo. Mira, mira y sal. Y solo después de un tiempo, acostumbrándose, comenzó a intentar hacer algo. En realidad, es por eso que traje solo una parte de la ventana. ¿Por qué asustar a todos por adelantado? Mientras tanto, solo necesitamos crear un Datapath, por lo que seleccionamos el elemento de menú Editar-> Nuevo Datapath :



¿Qué opción elegir en el cuadro de diálogo que aparece?



La pregunta es un poco más seria de lo que parece. Permítanme incluso resaltar el siguiente párrafo para que nadie quede atrapado (me sorprendí a mí mismo, y luego vi preguntas en la red de las que obtuve, y nadie realmente las respondió, y la respuesta está en AN82156 , solo necesita leerlo en diagonal, como dice allí frase corta y discreta).
Si planea trabajar con datos paralelos, definitivamente debe elegir la opción CY_PSOC3_DP. Ninguna otra opción contendrá puertos para conectar datos paralelos.
Entonces Deje que la instancia se llame LCD_DP:



Haga clic en Aceptar y cierre la herramienta de configuración de Datapath por ahora , aceptando guardar el resultado. Volveremos aquí más tarde.

Nuestro código Verilog se ha expandido. Ahora tiene Datapath. Su comienzo es completamente ilegible. No da miedo, está configurado por la herramienta de configuración de Datapath .



Y gobernaremos el final de la descripción de Datapath. Nuestro sitio se ve así
(desde este punto tiene sentido traer todo en 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 ); 


Miedo Ahora descubriremos qué es qué, dejará de dar miedo. De hecho, hay tres grupos distintos en este texto. Recordemos la traducción de la documentación. ¿Cómo se veía la ruta de datos en la imagen? Notaré de inmediato en la figura los lugares a los que pertenecen los grupos "1", "2" y "3".



En realidad, el primer grupo de puertos en el código verilog son las entradas. Compare los nombres en la salida del multiplexor de entrada ("1" en la figura) y los nombres de las señales en el código.

Ahora todas las entradas son cero. Tendremos que conectar la entrada del reloj y podemos reenviar hasta seis líneas de entrada, como se hizo en el Editor UDB. Estas entradas son:

  /* 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), 

El segundo grupo son las salidas. Los nombres en el código también coinciden con los nombres de las entradas del multiplexor de salida "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(), 

Solo la especie Datapath dada tiene el tercer grupo (los otros no tienen ninguno, por lo tanto, no hay datos paralelos). Estas son señales internas de Datapath a través de las cuales puede encadenar independientemente o realizar otras acciones útiles. Los nombres en el código también coinciden con los nombres de las señales internas dispersas en la figura. A través de uno de ellos (el último en la lista, su nombre es po ) enviaremos datos paralelos directamente a las patas del 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 ); 

Entonces A medida que trabajemos, tendremos que conectar algunas de estas entradas y salidas a nuestras propias entidades, y el resto, simplemente dejarlas en la forma en que las creamos.

Usando el editor UDB como referencia


Y ahora tenemos un espacio en blanco, sabemos dónde y qué tenemos que escribir. Queda por entender exactamente en qué entraremos allí. Dio la casualidad de que uso el idioma Verilog no todos los días, por lo que, en términos generales, recuerdo todo, y escribir desde cero para mí siempre es una situación estresante. Cuando el proyecto ya está en marcha, todo se recuerda, pero si después de un par de meses de inactividad empiezo algo desde cero, por supuesto, ya no recuerdo los detalles de sintaxis de este lenguaje en particular. Por lo tanto, sugiero pedir al entorno de desarrollo que nos ayude.

UDB Editor para autocontrol construye el código Verilog. Aprovechamos el hecho de que los componentes que no están involucrados en el circuito principal no están compilados, por lo que podemos crear un componente auxiliar en el editor UDB, y no entrará en el código de salida. Dibujaremos un autómata allí, haremos un ajuste aproximado de las entradas y salidas de Datapath, y luego simplemente transferiremos el texto generado automáticamente a nuestro módulo verilog y modificaremos todo creativamente. Esto es mucho más simple que recordar los detalles de la sintaxis de Verilog y escribir todo desde cero (aunque quien use Verilog constantemente, por supuesto, será más fácil escribir desde cero: la finalización creativa, como veremos pronto, es simple, pero requiere tiempo)

Entonces, comenzamos a hacer un componente auxiliar. Con el movimiento habitual de la mano agregamos un nuevo elemento al proyecto:



Este será un documento UDB, llamémoslo UDBhelper :



Es hora de pensar en la máquina, que colocaremos en la hoja creada. Para hacer esto, debemos considerar qué diagrama de tiempo debemos formar con él:





Entonces Primero debe configurar la señal RS (ya que R / W está soldado a cero en el hardware). A continuación, espere tAS, luego eleve la señal E y configure los datos (la configuración de datos con respecto al borde positivo E no está limitada). Los datos deben estar en el bus no menos que tDSW, después de lo cual se debe eliminar la señal E. Los datos deben permanecer en el bus durante al menos tDHW, y RS durante al menos tAH.

RS es el comando o el indicador de datos. Si RS es cero, se escribe un comando, si es uno, se escriben los datos.

Sugiero enviar comandos a través de FIFO0 y datos a través de FIFO1 . En el marco de la tarea actual, esto no contradice nada. Entonces la máquina de estados finitos propuesta por mí tendrá la siguiente forma:



En el estado inactivo , la máquina aún no tiene datos FIFO. Si aparecieron datos en FIFO0 , va a LoadF0 , donde en el futuro recibirá datos de FIFO0 a A0.

Mientras se transmiten los comandos, los datos no deben enviarse. Por lo tanto, la condición para recibir datos tendrá menor prioridad que la condición para recibir comandos.



Los datos se reciben en A1 en el estado LoadF1 (desde FIFO1 solo pueden ir al registro A1 y no pueden ir al registro A0), y luego se copian de A1 a A0 en el estado A1toA0 .

De cualquier manera que vayamos al punto de convergencia de las flechas, tenemos datos en A0. Ya se envían al puerto paralelo. Martillamos E (en estado E_UP1 ), soltamos E (en estado E_DOWN1 ). A continuación, tendremos un estado para intercambiar nibbles ( SWAP ), después de lo cual E se eleva nuevamente ( E_UP2 ). Sobre esto, he agotado ocho estados que pueden codificarse en tres bits. Y recordamos que la RAM de configuración dinámica de Datapath tiene solo tres entradas de dirección. Se podrían aplicar algunos trucos, pero el artículo ya es grande. Por lo tanto, solo la segunda vez colocaremos E en el estado Inactivo . Entonces ocho estados son suficientes para nosotros.

También colocamos Datapath en la hoja y asignamos sus entradas y salidas de una manera que es familiar en los artículos anteriores. Aquí están las entradas:



Aquí están las salidas:



Nada nuevo, todo ya se ha descrito en artículos anteriores del ciclo. Entonces, tenemos un espacio en blanco, en base al cual podemos hacer algo por nuestra cuenta. Es cierto, para asegurarnos de que todo esté funcionando, necesitamos llevar nuestro sistema al nivel superior del proyecto, de lo contrario no se encontrarán errores. Y en los experimentos iniciales sin errores no funcionará. Por lo tanto, haremos una acción auxiliar más.

La descripción de cómo se realiza el circuito va más allá de la descripción de trabajar con UDB. Solo te mostraré qué circuito tengo. Solo hay una unidad DMA: cuando se envían comandos a la pantalla LCD, es necesario soportar grandes pausas, por lo que aún es más fácil hacerlo mediante programación. Para otras aplicaciones, simplemente puede poner el segundo bloque DMA por analogía usando la señal hungry0 .



Para cumplir con precisión el marco de tiempo, elegí una frecuencia de reloj igual a un megahercio. Sería posible tomar una frecuencia y más, pero los datos se transmiten a través de cables largos en condiciones de alta interferencia, por lo que es mejor tomarse el tiempo para configurar los datos antes y después de la puerta con un margen. Si alguien repite mis experimentos en la misma placa de prueba, no use el puerto P3.2: un condensador está soldado a esta pata en la placa. Maté durante media hora, hasta que descubrí por qué no formé un impulso E, que primero conecté allí. Lo lancé a P3.1, todo funcionó de inmediato. Mi bus de datos va a P3.7-P3.4, RS va a P3.3, por lo que E originalmente fue a P3.2 ...

Bueno aqui. Ahora, si intenta compilar el proyecto, obtenemos errores completamente predecibles



Entonces el sistema está tratando de recolectar algo. Pero ella todavía no tiene nada que coleccionar. Procedemos a copiar el código. Para hacer esto, en el Editor UDB, cambie a la pestaña Verilog (esta pestaña se encuentra debajo de la ventana con la hoja del Editor UDB):



¿Qué es familiar allí? Al final del texto está el cuerpo del autómata. Comencemos la migración desde allí.

También colóquelo debajo de 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 


Hay declaraciones en la parte superior de este código (nombres para estados, cadenas para Datapath, un registro que codifica el estado de un autómata). Los transferimos al
sección de nuestro 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; 


Bueno y

el sitio de unión de señal es transferible:
 /* ==================== 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]); 


Es hora de conectar Datapath. El código portado desde el editor UDB es bueno para la edición automática, pero no muy bueno para la edición manual. Allí, se crean cadenas que se conectan a las entradas de Datapath en un extremo y a las constantes en el otro. Pero en el código creado por la Herramienta de configuración de Datapath (que hace todo para el trabajo manual), todas las entradas ya están conectadas directamente a constantes cero. Así que conectaré solo aquellas líneas que no son constantes, pero cortaré todo lo relacionado con el reenvío de constantes del texto transferido. La conexión resultó así (el color resalta los lugares que edité con respecto a los creados automáticamente en la Herramienta de configuración de Datapath):



Mismo 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), 


Los datos paralelos son un poco más complicados. Datapath tiene un puerto de ocho bits, y solo cuatro de ellos deben sacarse. Por lo tanto, comenzamos el circuito auxiliar y conectamos solo la mitad a la salida:

 wire [7:0] tempBus; assign LCD_D = tempBus[7:4]; 

Y conéctelo así:



Mismo texto:
  /* input [07:00] */ .pi(8'b0), // Parallel data port /* output [07:00] */ .po( tempBus) // Parallel data port ); 


Intentamos ensamblar (Shift + F6 o mediante el elemento de menú Build-> Generate Application ). Obtenemos el error:



Tenemos puertos hambriento0 y hambriento1 (aparecieron al crear el componente), así como cadenas del mismo nombre (aparecieron al arrastrar desde la muestra). Simplemente quite estas cadenas (dejando los puertos). Y en algún lugar la señal del reloj se filtró, y tenemos este circuito llamado clk .

Después de eliminar todos los circuitos innecesarios (aquellos que inicialmente arrojaron constantes cero a las entradas de Datapath, así como hungry0 y hungry1 ), obtenemos el siguiente código para el comienzo de nuestro archivo:

 // 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]; 

Y al reemplazar el reloj con clk en el cuerpo de la máquina, al mismo tiempo arrojaré todas las líneas que son buenas para la generación automática, pero con la edición manual solo crean confusión (todas las comparaciones que dan un resultado incondicional VERDADERO y así sucesivamente). En particular, en el ejemplo a continuación, puede tachar aproximadamente la mitad de las líneas (y algunas de inicio / final son opcionales, a veces serán necesarias, porque agregaremos acciones, las destaqué):



Después de peinar de acuerdo con el principio anterior (y reemplazar el reloj con clk ), dicho cuerpo permanece

(se ha vuelto más corto, lo que significa que es más fácil de leer):
 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 


Ahora, durante la compilación, se nos dice que los circuitos LCD_E y LCD_RS no están conectados.

En realidad, esto es cierto:



Ha llegado el momento de agregar acción a la máquina de estado. Reemplazaremos las declaraciones de los puertos correspondientes a las cadenas no conectadas con reg , ya que las escribiremos en el cuerpo de la máquina (esta es la sintaxis del lenguaje Verilog, si escribimos, los datos deben hacer clic, para esto necesitamos un disparador, y está dada por la palabra clave reg ):


Mismo texto:
 module LCD4bit ( output hungry0, output hungry1, output [3:0] LCD_D, output reg LCD_E, output reg LCD_RS, input clk ); 


Y llene la máquina con acciones. Ya dije la lógica anterior cuando estaba considerando el gráfico de transición del autómata, por lo que solo mostraré el resultado:


Mismo 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 de este momento, el proyecto comienza a ensamblarse. Pero aún no va a trabajar. Hasta ahora, he dicho: "En este estado, cargaremos el registro desde FIFO", "En esto, A1 se copiará a A0", "Los nibbles se reorganizarán en esto". En general, hablé mucho, pero hasta ahora no ha habido acciones. Ha llegado el momento de cumplirlos. Vemos cómo se codificaron los estados:

 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; 

Vuelva a abrir la herramienta de configuración de Datapath :



Y comience a editar las líneas CFGRAM . Al editar, debe tener en cuenta el esquema Datapath, a saber:



Los cuadros rojos en la figura siguiente (y las flechas en la figura anterior) resaltaron las áreas corregidas (y la ruta de datos) para el estado LoadF0 (código 001, es decir, Reg1 ). También ingresé comentarios manualmente. El contenido de F0 debe entrar en A0.



Con marcos y flechas verdes marqué la configuración y la ruta para el estado LoadF1 (código 010 - Reg2 ).

Con marcos azules y flechas marqué la configuración y la ruta para el estado A1toA0 (código 011 - Reg3 ).

Los cuadros y flechas morados marqué la configuración y la ruta para el estado de SWAP (código 110 - Reg6 ).

Finalmente, las flechas naranjas muestran la ruta de datos paralela. Y no se toman medidas por ellos. Siempre salen de la SRCA . Casi siempre tenemos A0 seleccionado como SRCA : los datos provienen de A0. Entonces, para redirigir los datos de entrada, tendríamos que realizar muchas acciones auxiliares, pero no aceptamos ningún dato, por lo que aquí no necesitamos estas acciones, y todos encontrarán su lista en AN82156 . Tampoco necesitamos editar ninguna configuración estática de Datapath, así que cierre la herramienta de configuración de Datapath .

Eso es todo. Hardware concebido completado. Comenzar a desarrollar código C. Para hacer esto, vaya a la pestaña Fuente y edite el archivo main.c.



La inicialización regular de LCD y la salida de caracteres "ABC" se ven así (le recuerdo que los comandos van a FIFO0 , la documentación necesita insertar pausas entre los equipos, y los datos van a FIFO1 , no encontré nada sobre las pausas entre los datos):

  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'; 

Que es ¿Por qué solo hay el primer personaje en la pantalla?



Y si agrega demoras entre la salida de datos, todo está bien:



El osciloscopio no tiene suficientes canales para tal trabajo. Verificamos el trabajo en un analizador lógico. El proceso de grabación de datos es el siguiente.



Todos los datos están en su lugar (tres pares de paquetes). El tiempo de instalación y ajuste de datos se asigna en un volumen suficiente. En general, desde el punto de vista de los diagramas de tiempo, todo se hace correctamente. El problema científico se resuelve, se forman los diagramas de tiempo deseados. Aquí está la ingeniería, no. La razón de esto es la lentitud del procesador instalado en la pantalla LCD. Entre bytes, agregue retrasos.

Formaremos demoras usando un contador de siete bits, al mismo tiempo entrenaremos para agregarlo a dicho sistema. Mantengámonos en estado inactivo no menos de un tiempo determinado, y un contador de siete bits medirá este tiempo para nosotros. Y nuevamente, no escribiremos, sino que crearemos código. Por lo tanto, nuevamente vamos al componente auxiliar del Editor UDB y agregamos un contador a la hoja de trabajo, configurando sus parámetros de la siguiente manera:



Este contador siempre funcionará ( Habilitar se establece en 1). Pero se cargará cuando la máquina esté en el estado E_UP2 (después de lo cual caeremos inmediatamente en el estado inactivo ). La línea Count7_1_tc se elevará a 1 cuando el contador cuente a cero, lo que crearemos una condición adicional para salir del estado inactivo . La figura también contiene el valor del período, pero no lo encontraremos en el código Verilog. Tendrá que ser ingresado en el código C. Pero primero, transferimos el código Verilog generado automáticamente al cambiar a la pestaña Verilog. En primer lugar, el contador debe estar conectado (vemos este código al principio del archivo y también lo movemos al principio):

 `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" 

Ya se ha descrito cómo se realiza el refinamiento creativo de líneas y constantes, así que solo mostraré el resultado. Aquí están las cadenas y las tareas agregadas como resultado (el resto establece las constantes, así que las tiré):

 wire Count7_1_load; wire Count7_1_tc; assign Count7_1_load = (SM==E_UP2); 

Y aquí está el contador en sí, colocado al final del archivo. Todas las constantes se asignan a los puertos directamente en esta declaración:

  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 este contador funcione, agregamos automáticamente una condición adicional para salir del estado Inactivo :


Mismo 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 


La API para el contador agregado de esta manera no se crea, por lo que agregamos dos líneas mágicas a la función principal , que formé en la imagen y semejanza de lo que vi en la API de proyectos anteriores (la primera línea establece el valor cargado de la cuenta, la misma Carga, la segunda inicia el contador):

  *((uint8_t*)LCD4bit_1_Count7_1_Counter7__PERIOD_REG) = 0x20; *((uint8_t*)LCD4bit_1_Count7_1_Counter7__CONTROL_AUX_CTL_REG) |= 0x20; // Start 

El analizador muestra que en el caso modificado el retraso es obvio:



La pantalla LCD también tiene los tres caracteres.

Pero la producción de caracteres programáticos en la vida real es inaceptable. Solo agregarlos a FIFO se desbordará. Espere a que el FIFO se vacíe, esto significa crear grandes demoras para el núcleo del procesador.El procesador funciona a una frecuencia de 72 MHz, y los datos se emiten durante 7-8 ciclos de reloj a una frecuencia de 1 MHz. Por lo tanto, en la vida real, el texto debe mostrarse usando DMA. Aquí es donde el principio de "lanzamiento y olvido" es útil. UDB generará todos los retrasos para el diagrama de temporización, y el controlador DMA determinará la disponibilidad de FIFO para recibirnos datos. El núcleo del procesador solo necesita formar una línea en la memoria y configurar DMA, después de lo cual puede realizar otras tareas sin preocuparse por la salida a la pantalla LCD.

Agregue el siguiente 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); 


En la pantalla tenemos:



Conclusión


, , UDB — Datapath Config Tool. , UDB Editor, UDB, , UDB Editor. , , , UDB Editor.

.

Source: https://habr.com/ru/post/442572/


All Articles