top of page

MicroZed Chronicles: Chat GPT and AI coding.

A few months ago, we looked at GitHub Copilot. The tool uses AI to assist with the coding process and it felt a little like pair programming with your development machine.


Recently, OpenAI released ChatGPT which received a lot of attention on Twitter, LinkedIn, and a whole host of other locations. Obviously a lot of people were trying its ability with software languages and I noticed few people tried it with Verilog. I thought it would be a valuable exercise to see how ChatGPT writes some simple to more complex VHDL and try to actually deploy it on a simple target board like the Arty S7-50.


To get started, I asked ChatGPT to give me the code to flash a LED at 0.5 HZ when running from a 100 MHz Clock. This should be simple for it to implement however, the first code output contained an error because the output signal cannot be read back internally to toggle it. The toggle rate would also be incorrect.


-- VHDL code for a 100 MHz counter that toggles an LED twice a second

library ieee;
use ieee.std_logic_1164.all;

entity counter is
  port(
    clk : in std_logic;
    led : out std_logic
  );
end entity;

architecture behavioral of counter is
  signal counter_signal : integer range 0 to 49999999 := 0;
begin
  process(clk)
  begin
    if rising_edge(clk) then
      if counter_signal = 49999999 then
        counter_signal <= 0;
      else
        counter_signal <= counter_signal + 1;
      end if;
    end if;
  end process;
  
  -- toggles the LED on and off every time the counter overflows
  led <= not led when counter_signal = 0 else '0';
end architecture;


Of course, I suggested that the output could not be read back and to use a signal to provide this feature.



-- VHDL code for a 100 MHz counter that toggles an LED twice a second
-- using a flip-flop for the LED signal

library ieee;
use ieee.std_logic_1164.all;

entity counter is
  port(
    clk : in std_logic;
    led : out std_logic
  );
end entity;

architecture behavioral of counter is
  signal counter_signal : integer range 0 to 49999999 := 0;
  signal led_signal : std_logic := '0';
begin
  process(clk)
  begin
    if rising_edge(clk) then
      if counter_signal = 49999999 then
        counter_signal <= 0;
      else
        counter_signal <= counter_signal + 1;
      end if;
    end if;
  end process;
  
  -- uses the counter_signal to clock the flip-flop, toggling its output on
  -- and off every time the counter overflows
  led_signal <= not led_signal when counter_signal = 0 else led_signal;
  
  -- assigns the flip-flop's output to the led signal
  led <= led_signal;
end architecture;


Now the code looks pretty straight forward and, while it’s not how I would have written it, it was functional so I decided to try it in the Arty board. How it assigns LED_SIGNAL creates a latch not a flip flop as I requested but it was functional so tried it on the hardware.

I created an IP Integrator design because I wanted to add to it with further requests. When running on the hardware, this did blink the LED0 on the Arty S7-50 board as the expected.



The next question I asked was for the ChatGPT to generate the counter which could count to 87. Once the code was generated, I asked for a test bench to be created.




-- VHDL counter that resets when it reaches 87
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity counter is
  port (
    clk : in std_logic;   -- clock input
    rst : in std_logic;   -- reset input
    en  : in std_logic;   -- enable input
    cnt : out std_logic_vector(7 downto 0)  -- counter output
  );
end counter;

architecture rtl of counter is
    signal cnt_internal : unsigned(7 downto 0) := (others=>'0');
begin
  
  -- counter process
  process (clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        -- reset counter to 0 on rising edge of clock
        cnt_internal <= (others => '0');
      elsif en = '1' then
        -- increment counter on rising edge of clock
        if cnt_internal = 87 then
          -- reset counter to 0 when it reaches 87
          cnt_internal <= (others => '0');
        else
          cnt_internal <= cnt_internal + 1;
        end if;
      end if;
    end if;
  end process;
  
  cnt <= std_logic_vector(cnt_internal);
  
end rtl;

-- VHDL test bench for counter that resets when it reaches 87

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity tb_counter is
end tb_counter;

architecture tb of tb_counter is
  -- component declarations
  component counter is
    port (
      clk : in std_logic;
      rst : in std_logic;
      en  : in std_logic;
      cnt : out std_logic_vector(7 downto 0)
    );
  end component;

  -- signal declarations
  signal clk : std_logic := '0';
  signal rst : std_logic := '1';
  signal en  : std_logic := '0';
  signal cnt : std_logic_vector(7 downto 0) := (others => '0');

begin
  -- instantiate the counter
  UUT : counter
    port map (
      clk => clk,
      rst => rst,
      en  => en,
      cnt => cnt
    );

  -- clock process
  clk_process : process
  begin
    clk <= '0';
    wait for 10 ns;
    clk <= '1';
    wait for 10 ns;
  end process;

  -- test stimulus
  stimulus : process
  begin
    -- reset the counter
    rst <= '1';
    wait for 100 ns;
    rst <= '0';
    -- wait a few clock cycles
    wait for 30 ns;
    -- enable the counter
    en <= '1';
    -- wait until the counter reaches 87
    wait until cnt = std_logic_vector(to_unsigned(87,8));
    -- disable the counter
    en <= '0';
    -- wait a few more clock cycles
    wait for 30 ns;
    -- end the simulation
    wait;
  end process;

end tb;

The main code had issues because it expected the std_logic_vector to be able to increment. As we know though, it cannot. You need to use the numeric_std types or include the std_logic_arith library. It also tried to read back the output vector in a similar manner to the initial LED example.

The only minor issue with the test bench code was a missing type conversion on the line


wait until cnt = 87; 

This needed to be


wait until cnt = std_logic_vector(to_unsigned(87,8));

Normally, I would have asked ChatGPT to correct these errors but since the server was heavily loaded, I did it manually.


Running the simulation shows that the code does exactly what is asked of it. Notice it does not reset and continues holding it count at 87 . This is exactly like I asked and a lesson in clarity of requirements when working with tools like ChatGPT.


The next question I asked the GPTChat was to see if it could create me a simple UART which would receive eight bits and latch them to the LED outputs on the RGB LEDS. We need six bits to do this so it makes for a simple application.

The first UART code generated was not going to function correctly because it did not consider the sampling of the bits. Curiously, it also forgot to include the basic IEEE Library and use of std_logic_1164 and numeric_std which it had done for the first example though these can be easily added in.


-- VHDL UART with 100 MHz clock and 115200 baud rate

-- Declare entity and ports
entity uart is
  port (
    clk_in  : in  std_logic;  -- 100 MHz clock input
    rx_in   : in  std_logic;  -- RX input for receiving data
    led_out : out std_logic_vector(7 downto 0)  -- 8 bit LED output
  );
end entity uart;

-- Declare architecture
architecture rtl of uart is
  -- Declare signals
  signal rx_reg   : std_logic_vector(7 downto 0) := (others => '0');  -- RX register for storing received data
  signal bit_cnt  : integer range 0 to 11 := 0;  -- Bit counter for tracking current bit number
  signal start_rcv: std_logic := '0';  -- Start receive signal for beginning data reception
begin
  -- Clock process
  process(clk_in)
  begin
    if rising_edge(clk_in) then
      if start_rcv = '1' then
        -- Increment bit counter and store received data in register
        if bit_cnt < 11 then
          rx_reg <= rx_reg(6 downto 0) & rx_in;
          bit_cnt <= bit_cnt + 1;
        else
          -- When all bits are received, reset bit counter and start receive signal
          bit_cnt <= 0;
          start_rcv <= '0';
        end if;
      else
        -- Check for start bit and set start receive signal if detected
        if rx_in = '0' then
          start_rcv <= '1';
        end if;
      end if;
    end if;
  end process;

  -- Set LED output to received data
  led_out <= rx_reg;
end architecture rtl;

I pointed out this error to ChatGPT and asked it to correct it.

The code generated this time looked a little more plausible, but it is still not going to work. After a long chat with GPT I realised this would be the best I could get from it so it would need to be modified to work. The main issue was that the code kept resetting the counter because it had no understanding of when reception of the data stream had started.


library ieee;
use ieee.std_logic_1164.all;

entity uart is
  port (
    clk: in std_logic;
    rx: in std_logic;
    led: out std_logic_vector(7 downto 0)
  );
end uart;

architecture behavioral of uart is
  -- Baud rate divisor (100 MHz / 115200 baud)
  constant BAUD_DIVISOR: integer := 858;

  -- Counter to track baud rate
  signal baud_counter: integer range 0 to BAUD_DIVISOR;

  -- Received byte buffer
  signal rx_buffer: std_logic_vector(7 downto 0) :=(others => '0');

  -- Start bit detection flag
  signal rx_start: std_logic := '0';

  -- Received byte buffer valid flag
  signal rx_valid: std_logic := '0';
begin
  -- Increment and reset baud rate counter on each clock edge
  process(clk, rx_start, rx_valid)
  begin
    if rising_edge(clk) then
      if rx_start = '1'  then
        baud_counter <= 0;
      elsif baud_counter = BAUD_DIVISOR - 1 then
        baud_counter <= 0;
      else
        baud_counter <= baud_counter + 1;
      end if;
    end if;
  end process;

  -- Shift in received bits on each baud rate clock edge
  process(clk, rx)
  begin
    if rising_edge(clk) then
      if baud_counter = 0 then
        -- Shift in received bit
        rx_buffer <= rx_buffer(6 downto 0) & rx;
      end if;
    end if;
  end process;

  -- Detect start bit and valid byte reception on each clock edge
  process(clk, rx)
  begin
    if rising_edge(clk) then
      if rx_start = '0' and rx = '0' then
        -- Start bit detected
        rx_start <= '1';
      elsif rx_start = '1' and rx_buffer(7) = '1' then
        -- Valid byte received, set LED bank
        led <= rx_buffer;
        rx_start <= '0';
      end if;
    end if;
  end process;
end architecture behavioral;


I then asked it to create a test bench to test its code and my eventual fixed code.

The test bench code was pretty poor and not able to correctly time the UART bit period to inject a test vector. Here I am giving GPT the benefit of the doubt as my instruction required it to be able to remember critical elements of the previous design. This is something which would be expected if working with another engineer but it might be too much for AI Chat at the moment.


I had to twiddle both the test bench and the UART code to get it to work. As a simple test I was injecting 0xAA as the data byte.

You can see in my comparisons below between the final code (left) and the original code (right) that there was an entire process which needed removing.


I had plans to increase this to a UART to AXI bridge but I decided at this point the UART was pushing it so I stopped the experiment.


Overall I was impressed with ChatGPT in that it can generate and iterate code as though you are talking to another engineer developing in the office. It does feel like your talking to a pretty new FPGA developer though, having to spell things out and make comments to refine what you want and it is pretty limited in its capabilities.


That said, it can give you a great starting point for development and a workable framework in a few minutes while eventually taking over the code and finessing it. It was also great fun (and a little frustrating) trying to explain exactly what I wanted.


This is impressive for a research preview version and I’m sure it will only get better in the future.


Workshops and Webinars

Enjoy the blog why not take a look at the free webinars, workshops and training courses we have created over the years. Highlights include


Embedded System Book

Do you want to know more about designing embedded systems from scratch? Check out our book on creating embedded systems. This book will walk you through all the stages of requirements, architecture, component selection, schematics, layout, and FPGA / software design.


We designed and manufactured the board at the heart of the book! The schematics and layout are available in Altium here


Learn more about the board (see previous blogs on Bring up, DDR validation, USB, Sensors) and view the schematics here.



Sponsored by AMD Xilinx

0 comments
bottom of page