2022/05/04

Parte de Flisol 2022

En esta oportunidad expuse un mezcladito de varios open:

Open HW/Core/SW: SoC ICICLE con CPU RISC-V en EDU-CIAA-FPGA

A mi propuesta original que incluía el ataque mostrado en H4CK3D 2021, el Profesor Matías me recomendó podarla para que sea menos técnica. Esa poda fué insuficiente, ya el título es bastante largo y complicado, a la mayor parte de la asistencia no era un tema que le interesara, no importa, para FLISoL 2023 tengo pensado algo más apropiado.

 

Mi idea fue ir evolucionando distintas implementaciones de la luz ondulante de K.I.T.T. hasta llegar a un softcore en FPGA, aprovechando el trabajo de un montón de gente:

 

Historia
Historia

 Lo rojito a la derecha era mi objetivo, lo expuesto en H4CK3D 2021

 

La primera versión es con componentes digitales, la hice hace casi 35 años, un registro de desplazamiento con unas puertas OR animadas a continuación, con un 555 para el clock, unos OR, un flip flop y algún capacitor para  inyectar el primer bit.

Ese bit entra en la primera posición del registro de desplazamiento y en cada tick del clock se va moviendo, con la salida conectada a la entrada con un OR al flip flop que salvo en el arranque siempre está en cero, lo tenemos para siempre. Yendo hacia los leds, con los OR de la derecha transformamos el movimiento en un aparente ida y vuelta.

K.I.T.T. con compuertas digitales
K.I.T.T. con compuertas digitales

 

Comparando precios, el costo es similar a implementarlo con un microcontrolador. Ponemos un bit en uno y lo vamos desplazando para un lado hasta detectar que llegó al punto deseado, ahí invertimos el sentido del movimiento, para siempre.


K.I.T.T. con microcontrolador y lógica específica
K.I.T.T. con microcontrolador y lógica específica

El programa es corto pero complicado e inadaptable a otros patrones. Una versión mejor aunque te indigne desde el punto de vista de la programación es:

while (true) {
  gpio_A.out(1);
  delay(DELAY);
  gpio_A.out(2);
  delay(DELAY);
  gpio_A.out(4);
  delay(DELAY);
  gpio_A.out(8);
  delay(DELAY);
  gpio_A.out(16);
  delay(DELAY);
  gpio_A.out(8);
  delay(DELAY);
  gpio_A.out(4);
  delay(DELAY);
  gpio_A.out(2);
  delay(DELAY);
  gpio_A.out(1);
  delay(DELAY);
}

¿Por qué me atrevo a incluir esta manera? Pues por que es el precursor para esta version más linda, en lugar de código hardcodeado, la información está en un array:

unsigned int out[]={1,2,4,8,16,8,4,2,1};
int pos = 0;
int limit = sizeof(out)/sizeof(out[0]);

while ( true) {
  gpio_A.out(1);
  delay(DELAY);
  ++pos;
  if (pos == limit) {
    pos = 0;
  }
}

y esto se parece mucho a la implementación nuevamente con componentes digitales.

K.I.T.T. con contador y memoria
K.I.T.T. con contador y memoria
 

Ahora, tanto en hardware como en software, podemos con gran facilidad mostrar otros patrones manipulando la memoria o el array según corresponda:

 

K.I.T.T. con contador y memoria y patrones arbitrarios
K.I.T.T. con contador y memoria y patrones arbitrarios

 

En la charla acompañando a esta evolución también fui contando como funciona una computadora a un nivel más bajo, que me cuesta mucho convertir a este formato, quizás haga un video en algún momento, lamentablemente no pude grabar la sesión.


Eso llevó a la explicación de FPGA y RISC-V, desembocando en el tema que me interesaba, la implementación primero en Verilog de la versión con lógica, como dispositivo incluido en el SoC icicle:

https://github.com/cpantel/evilCodeSequence/blob/master/kitt.sv

module kitt #( parameter BASETIME) (
    input clk,
    input reset,
    output [4:0]display_out,
    /* memory bus */
    input [31:0] address_in,
    input sel_in,
    //input read_in,
    //output logic [31:0] read_value_out,
    input [3:0] write_mask_in,
    input [31:0] write_value_in,
    output logic ready_out
);

    logic [25:0]q;
    logic direction;
    logic [4:0]display;

    assign ready_out = sel_in;
    assign display_out = display;

    always_ff @(posedge clk) begin
        if (reset) begin
            display <= 1;
            direction <= 1;
        end else if ( q > ( BASETIME / 1000 * 300 ) ) begin
            q <= 0;
            if (direction ) begin
                 if ( display[4] ) begin
                     direction = ~ direction;
                     display <= display >> 1;
                 end else
                     display <= display << 1;
            end else begin
                 if ( display[0] ) begin
                     direction = ~ direction;
                     display <= display << 1;
                 end else
                     display <= display >> 1;
            end 
       end else begin
            q <= q + 1;
            display <= display;
       end
    end

endmodule

 

Y por software, de modo concurrente y absolutamente independiente, ejecutándose en la CPU:

 

/*
  This program implements a SW kitt
*/

#include <stdint.h>
#include "../memmap.h"
#include "../uart.h"
#include "../delay.h"


int main() {
    uart_init();

    uart_puts("KITT starting\r\n");
    for (;;) {
        LEDS = 1;
        delay();
        LEDS = 2;
        delay();
        LEDS = 4;
        delay();
        LEDS = 8;
        delay();
        LEDS = 4;
        delay();
        LEDS = 2;
        delay();
        uart_puts("KITT .\r\n");
    }
}

 

 

Si algo entendés de Verilog, te darás cuenta que ese dispositivo como que está en vano conectado a los buses, ya que no hay nada con lo que el programa en la CPU pueda interactuar. Es que me quedé sin tiempo, querría al menos haberle permitido cambiar la velocidad.

En una próxima entrega espero no muy lejana, mostraré la implementación de un nuevo dispositivo que he de llamar "sequencer", que consistirá en una lógica que lea un trozo de memoria y lo exponga en los leds (vía PMOD como es ahora kitt) y que desde el programa se pueda arrancar, detener, pausar, cambiar la velocidad y modificar esa memoria.