SDA Ethernet encapsulation with chatGPT o1 (part 2)
In my last post I covered some aspects of using chat GPT o4 to produce a module according to the SDA specification of Ethernet encapsulation. This module represents a transmitted encapsulated Ethernet packet with respect to the SDA specification. That post can be found here:
SDA Ethernet encapsulation with chatGPT o1
In this post I want to explore changing the LLM to chatGPT o1 , giving a very detail explanation of the module to be produced and analyze the outcome and potentially make corrections to the prompt. One of the goal changes is to produce one module (entity) that only encapsulates the Ethernet packets and does not produce the SDA frame.
The prompt used and associated code can be found here:
SDA Ethernet encapsulation with chatGPT o1
At first glance the code looks fairly good and chat GPT provides a very good explanation of what it's doing with the checks it also suggests what could be further implemented.
One of the key requirements of the prompt is to have a function that operates as a modified FIFO that can be reset to the last packet that is received correctly. ChatGPT is able to infer this but the treatment of a FIFO is not correct. The input is not decoupled from the output. In other words, you cannot write into the FIFO and read from the FIFO concurrently. It uses a single statement machine implementation where there's a send packet state that is tied into the other states with respect to receiving packets. For my perspective this is a flaw in what it perceives as a definition of fifo.
I then adjusted the prompt and had it output a test bench with requirements. These are the results:
errors :
- [USF-XSim-62] 'compile' step failed with error(s). Please check the Tcl console output or 'C:/Projects/IRAD/SDA/vivado/sda/sda.sim/sim_1/behav/xsim/xvhdl.log' file for more information.
- [Vivado 12-4473] Detected error while running simulation. Please correct the issue and retry this operation.
syntax errors , vivado gave this line as a syntax error where it did not like the ( \" )
report \"All packets submitted. End of test.\" severity note;
ERROR: [VRFC 10-724] found '0' definitions of operator "xor", cannot determine exact overloaded matching definition for "xor" [C:/Projects/IRAD/SDA/tb/sda_fsoe_make_tb.vhd:142]
line 142: if sig_in(31) xor sig_in(21) xor sig_in(1) xor sig_in(0) = '1' then
rather than performing boolean logic on unsigned integers I cast the vector into a standard logic vector
fix:
variable sig_in_slv : std_logic_vector(31 downto 0);
sig_in_slv := STD_LOGIC_VECTOR(sig_in);
The problem is that the VHDL tool is having trouble resolving the overloaded "xor" operator when it's used in a chain without explicit grouping. Even though VHDL defines "xor" as left-associative, some tools require you to add parentheses to help with overload resolution.
to fix the line by introduce explicit grouping so that the binary operations are performed one at a time. For example, you can rewrite the condition like this:
was: if sig_in_slv(31) xor sig_in_slv(21) xor sig_in_slv(1) xor sig_in_slv(0) = '1' then
fix, force the simulator to perform bit : if (((sig_in_slv(31) xor sig_in_slv(21)) xor sig_in_slv(1)) xor sig_in_slv(0)) = '1' then
WARNING: [VRFC 10-393] cannot access 'rand_val' from inside pure function 'rand_int'. [C:/Projects/IRAD/SDA/tb/sda_fsoe_make_tb.vhd:155]
ERROR: [VRFC 10-1281] process cannot have both a wait statement and a sensitivity list [C:/Projects/IRAD/SDA/tb/sda_fsoe_make_tb.vhd:329]
I fixed a few issues, but the simulation still failed. Random data was never written into the BRAM and a comparison check failed in the simulation giving this error. ERROR: [Simulator 45-1] A fatal run-time error was detected. Simulation cannot continue.
In general, chatGPT o1 still fails and requires the user to fix some errors and the overall simulation does not work. I did not go into the specifics to finish this debug or check to see if the overall solution would work. values being written into the BRAM from the input (slave side of the interface) were stalled or wrong.
chatGPT module prompt:
1st prompt
section 3.4.8 Ethernet Encapsulation it describes how to encapsulate Ethernet packets. There are to be 2 modules (Entities) that perform the task of encapsulating Ethernet packets and producing a FSO payload frame . Act as a VHDL digital design engineer and code in vhdl a module that inputs AXIS-stream Ethernet packets where the data width is 32 bits wide, the first data word is marked by the first assertion of s_tvaild and s_tready after the previous s_tlast. The last data word is marked by s_tlast, s_tvalid, and s_tready asserted. s_tkeep identifies the valid bytes in the last data beat. You are to code the first entity. Name it "sda_fsoe_make". The function is to gather Ethernet packet data, perform functions to populate the FSO packet header, output as a AXIS-stream master the "wrapped" Ethernet packet with its header. The term wrapped Ethernet packet describes the Ethernet packet with a valid SDA FSO packet header. The input is an axi stream interface and the output is an axi-stream interface. inputs are identified as "s_t" then the name of the signal follows. For example data, keep, last, valid. The output is identified as "m_t" then the signal name. This module should perform a byte count as the data is received and use this byte count to populate the FSO packet header. The data and header should be stored in a FPGA internal memory structure known as a block ram or BRAM for short. This structure should function as a modified FIFO. As data is received it is written into the FIFO. The first data beat should be written into the 2nd location. Once the last word is received the header is populated in the first location for that packet. The received Ethernet packet should be checked for maximum MTU size. If it is over the maximum the data in the FIFO should be discarded, the write pointer should be reset to it's previous location prior to receiving that packet. The FIFO width should be 32-bits wide and 2048 words deep. It should be able to hold at least 5 Ethernet packets if each one is its maximum size. This unique FIFO should be able to return the write pointer to the next location after the end of the last good packet received. If there are any errors in the packet that is being received the rest of the input data is ignored and the data in the fifo is to be removed by returning the write pointer to the last good location prior to receiving data from the bad transaction. Analyze the code produced and check for correctness in function.
2nd prompt
The code in sda_fsoe_make.vhd does not have the ability to receive an AXIS stream and source an AXIS stream, or act as a slave and master at the same time. In general FIFO should be able to accept data written to them and also read data from the FIFO at the same time. This requires 2 processes, a write process and a read process. The read process will source data on the AXIS-stream master signals. Modify the code sda_fsoe_make.vhd to support these changes.
chatGPT module code:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; -- For arithmetic on integers, unsigned entity sda_fsoe_make is generic ( G_MAX_MTU : integer := 1500; -- Max Ethernet Payload in bytes G_BRAM_DEPTH : integer := 2048; -- BRAM depth (in 32-bit words) G_DATA_WIDTH : integer := 32; -- Data width (bits) G_HEADER_WORDS : integer := 1 -- # of words reserved for FSO header ); port ( clk : in std_logic; reset : in std_logic; -- AXI-Stream Slave: Ethernet input s_tvalid : in std_logic; s_tready : out std_logic; s_tdata : in std_logic_vector(G_DATA_WIDTH-1 downto 0); s_tkeep : in std_logic_vector((G_DATA_WIDTH/8)-1 downto 0); s_tlast : in std_logic; -- AXI-Stream Master: Wrapped data output m_tvalid : out std_logic; m_tready : in std_logic; m_tdata : out std_logic_vector(G_DATA_WIDTH-1 downto 0); m_tkeep : out std_logic_vector((G_DATA_WIDTH/8)-1 downto 0); m_tlast : out std_logic ); end sda_fsoe_make; architecture Behavioral of sda_fsoe_make is ---------------------------------------------------------------------------- -- TYPE & CONSTANT DECLARATIONS ---------------------------------------------------------------------------- type bram_type is array (0 to G_BRAM_DEPTH-1) of std_logic_vector(G_DATA_WIDTH-1 downto 0); -- Write state machine type w_state_type is ( W_IDLE, -- Wait for start of packet W_RECEIVE, -- Receiving data into FIFO W_ERROR, -- Error/oversize, discard partial W_DONE -- Wrap up the write side ); -- Read state machine type r_state_type is ( R_IDLE, -- Wait for a packet to be available R_WRITEHDR, -- Insert header into 1st word for this packet R_READOUT, -- Streaming out data R_DONE -- Clean up ); ---------------------------------------------------------------------------- -- SIGNAL DECLARATIONS ---------------------------------------------------------------------------- -- Shared FIFO signal bram_mem : bram_type := (others => (others => '0')); -- Write side signal w_state : w_state_type := W_IDLE; signal write_ptr : integer range 0 to G_BRAM_DEPTH-1 := 0; -- Points to next write location signal write_ptr_start : integer range 0 to G_BRAM_DEPTH-1 := 0; -- For header insertion offset signal saved_write_ptr : integer range 0 to G_BRAM_DEPTH-1 := 0; -- Last valid end-of-packet pointer signal w_byte_count : integer := 0; -- Byte count for current packet -- Read side signal r_state : r_state_type := R_IDLE; signal read_ptr : integer range 0 to G_BRAM_DEPTH-1 := 0; -- Next read location signal r_byte_count : integer := 0; -- Byte count for current reading packet -- FSO Header and related signal fso_header_word : std_logic_vector(G_DATA_WIDTH-1 downto 0) := (others => '0'); -- AXI-Stream outputs signal m_valid_reg : std_logic := '0'; signal m_data_reg : std_logic_vector(G_DATA_WIDTH-1 downto 0) := (others => '0'); signal m_keep_reg : std_logic_vector((G_DATA_WIDTH/8)-1 downto 0) := (others => '1'); signal m_last_reg : std_logic := '0'; -- AXI-Stream inputs signal s_ready_reg : std_logic := '0'; ---------------------------------------------------------------------------- -- SIMPLE HELPER FUNCTION: MAKE FSO HEADER -- Example format: -- 31:16 -> Magic 0xCDEF -- 15:2 -> Packet length in bytes (14 bits) -- 1:0 -> Reserved ---------------------------------------------------------------------------- function make_fso_header(byte_len : integer) return std_logic_vector is variable tmp : std_logic_vector(31 downto 0) := x"CDEF0000"; variable l14 : unsigned(13 downto 0); begin -- Convert length to 14 bits l14 := to_unsigned(byte_len, 14); -- Insert length at bits [15:2] tmp(15 downto 2) := std_logic_vector(l14); return tmp; end function; begin ---------------------------------------------------------------------------- -- AXI-Stream slave/master port mapping ---------------------------------------------------------------------------- -- Outputs s_tready <= s_ready_reg; m_tvalid <= m_valid_reg; m_tdata <= m_data_reg; m_tkeep <= m_keep_reg; m_tlast <= m_last_reg; ---------------------------------------------------------------------------- -- WRITE PROCESS (AXI-Stream Slave) - store data in FIFO ---------------------------------------------------------------------------- WriteProcess : process(clk) begin if rising_edge(clk) then if reset = '1' then w_state <= W_IDLE; write_ptr <= 0; write_ptr_start <= 0; saved_write_ptr <= 0; w_byte_count <= 0; s_ready_reg <= '0'; else -- Default s_ready_reg <= '0'; case w_state is ---------------------------------------------------------------- -- W_IDLE: Wait for new packet start ---------------------------------------------------------------- when W_IDLE => -- Set next packet to begin at saved_write_ptr write_ptr_start <= saved_write_ptr; write_ptr <= saved_write_ptr + G_HEADER_WORDS; -- skip the header area w_byte_count <= 0; s_ready_reg <= '1'; -- Ready to accept data if (s_tvalid = '1') then -- Accept first beat bram_mem(write_ptr) <= s_tdata; write_ptr <= write_ptr + 1; w_byte_count <= w_byte_count + 4; if s_tlast = '1' then -- If single beat packet if w_byte_count + 4 > G_MAX_MTU then w_state <= W_ERROR; else w_state <= W_DONE; end if; else w_state <= W_RECEIVE; end if; end if; ---------------------------------------------------------------- -- W_RECEIVE: Storing packet data ---------------------------------------------------------------- when W_RECEIVE => s_ready_reg <= '1'; if (s_tvalid = '1') then -- Write next word bram_mem(write_ptr) <= s_tdata; write_ptr <= write_ptr + 1; w_byte_count <= w_byte_count + 4; if w_byte_count + 4 > G_MAX_MTU then w_state <= W_ERROR; elsif s_tlast = '1' then w_state <= W_DONE; end if; end if; ---------------------------------------------------------------- -- W_ERROR: Discard partial packet ---------------------------------------------------------------- when W_ERROR => s_ready_reg <= '1'; -- Wait for s_tlast to skip rest if (s_tvalid = '1') then if s_tlast = '1' then -- Discard partial by reverting write_ptr <= saved_write_ptr; w_byte_count <= 0; w_state <= W_IDLE; end if; end if; ---------------------------------------------------------------- -- W_DONE: Packet in FIFO, wait for next ---------------------------------------------------------------- when W_DONE => -- Packet is fully received saved_write_ptr <= write_ptr; w_state <= W_IDLE; when others => w_state <= W_IDLE; end case; end if; end if; end process; ---------------------------------------------------------------------------- -- READ PROCESS (AXI-Stream Master) - read from FIFO ---------------------------------------------------------------------------- ReadProcess : process(clk) begin if rising_edge(clk) then if reset = '1' then r_state <= R_IDLE; read_ptr <= 0; r_byte_count <= 0; m_valid_reg <= '0'; m_data_reg <= (others => '0'); m_keep_reg <= (others => '1'); m_last_reg <= '0'; else -- Defaults each cycle m_valid_reg <= '0'; m_data_reg <= (others => '0'); m_keep_reg <= (others => '1'); m_last_reg <= '0'; case r_state is ---------------------------------------------------------------- -- R_IDLE: Wait for a completed packet in FIFO ---------------------------------------------------------------- when R_IDLE => if (w_state = W_DONE) then -- We know a new packet is done r_byte_count <= w_byte_count; read_ptr <= write_ptr_start; -- Start reading from the first word (header loc) r_state <= R_WRITEHDR; else -- Remain idle r_state <= R_IDLE; end if; ---------------------------------------------------------------- -- R_WRITEHDR: Insert the FSO Header at the reserved location ---------------------------------------------------------------- when R_WRITEHDR => -- Make a local FSO header based on the final byte_count fso_header_word <= make_fso_header(r_byte_count); -- Write into the first word bram_mem(write_ptr_start) <= fso_header_word; -- Now start streaming from read_ptr read_ptr <= write_ptr_start; -- We'll read from 0 on next cycle r_state <= R_READOUT; ---------------------------------------------------------------- -- R_READOUT: Stream out from FIFO ---------------------------------------------------------------- when R_READOUT => -- We'll assert tvalid if we have data to send if read_ptr < saved_write_ptr then m_valid_reg <= '1'; m_data_reg <= bram_mem(read_ptr); if read_ptr = saved_write_ptr-1 then m_last_reg <= '1'; -- last word end if; -- If the master is ready, consume the word if (m_tready = '1') then read_ptr <= read_ptr + 1; -- If that was last word, go DONE if read_ptr = saved_write_ptr-1 then r_state <= R_DONE; end if; end if; else r_state <= R_DONE; -- No data to send end if; ---------------------------------------------------------------- -- R_DONE: End of packet ---------------------------------------------------------------- when R_DONE => -- Cleanup and wait for next m_valid_reg <= '0'; r_state <= R_IDLE; when others => r_state <= R_IDLE; end case; end if; end if; end process; end Behavioral;
chatGPT testbench prompt:
Provide a VHDL test bench that tests and verifies the required functions of sda_fsoe_make entity. The testbench should include continuous random packets input to the module of random length and random inter-packet gaps. The output should check for correctness of input packets. The output interface should operate at the same time as the input slave interface. The ready signal on the master (output interface) should have a random assertion. There should be a set parameter that indicates the number of input packets used in the simulation. The input packets should also test the error capabilities of the entity. Name the entity sda_fsoe_make_tb.
chat GPT testbench code:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity sda_fsoe_make_tb is end sda_fsoe_make_tb; architecture Behavioral of sda_fsoe_make_tb is ------------------------------------------------------------------------------ -- Generic Constants ------------------------------------------------------------------------------ constant CLK_PERIOD : time := 10 ns; -- 100 MHz constant NUM_TEST_PKTS : integer := 30; -- Number of random input packets constant MAX_PKT_SIZE : integer := 2500; -- Some will exceed this to test error constant RANDOM_SEED : integer := 13; -- For pseudo-random generation ------------------------------------------------------------------------------ -- Signals ------------------------------------------------------------------------------ signal clk : std_logic := '0'; signal reset : std_logic := '1'; -- AXI-Stream Slave (input to DUT) signal s_tvalid : std_logic := '0'; signal s_tready : std_logic; signal s_tdata : std_logic_vector(31 downto 0) := (others => '0'); signal s_tkeep : std_logic_vector(3 downto 0) := (others => '1'); signal s_tlast : std_logic := '0'; -- AXI-Stream Master (output from DUT) signal m_tvalid : std_logic; signal m_tready : std_logic := '1'; signal m_tdata : std_logic_vector(31 downto 0); signal m_tkeep : std_logic_vector(3 downto 0); signal m_tlast : std_logic; ------------------------------------------------------------------------------ -- DUT Declaration ------------------------------------------------------------------------------ component sda_fsoe_make generic ( G_MAX_MTU : integer := 1500; -- Max Ethernet Payload G_BRAM_DEPTH : integer := 2048; G_DATA_WIDTH : integer := 32; G_HEADER_WORDS : integer := 1 ); port ( clk : in std_logic; reset : in std_logic; -- AXI-Stream Slave s_tvalid : in std_logic; s_tready : out std_logic; s_tdata : in std_logic_vector(31 downto 0); s_tkeep : in std_logic_vector(3 downto 0); s_tlast : in std_logic; -- AXI-Stream Master m_tvalid : out std_logic; m_tready : in std_logic; m_tdata : out std_logic_vector(31 downto 0); m_tkeep : out std_logic_vector(3 downto 0); m_tlast : out std_logic ); end component; ------------------------------------------------------------------------------ -- Signals & Variables for Test ------------------------------------------------------------------------------ -- Pseudo-random generator signal rand_val : unsigned(31 downto 0) := to_unsigned(RANDOM_SEED,32); -- Internal scoreboard of input packets type pkt_array is array (0 to NUM_TEST_PKTS-1) of std_logic_vector(8*MAX_PKT_SIZE-1 downto 0); signal stored_pkts : pkt_array := (others => (others => '0')); signal stored_pkt_size : integer_vector(0 to NUM_TEST_PKTS-1) := (others => 0); -- Index to track how many packets we've sent signal pkt_index_in : integer := 0; signal pkt_index_out : integer := 0; -- Transmission states type tb_send_state_type is (TB_SEND_IDLE, TB_SEND_DATA, TB_SEND_DONE); signal tb_send_state : tb_send_state_type := TB_SEND_IDLE; -- Output capture type out_capture_t is array (0 to NUM_TEST_PKTS*2-1) of std_logic_vector(31 downto 0); signal out_capture : out_capture_t := (others => (others => '0')); signal out_capture_idx : integer := 0; ------------------------------------------------------------------------------ -- Functions ------------------------------------------------------------------------------ function pseudo_rand(sig_in : in unsigned(31 downto 0)) return unsigned is variable tmp : unsigned(31 downto 0); begin -- Simple LFSR or shift-based pseudo random tmp := sig_in(30 downto 0) & '0'; if sig_in(31) xor sig_in(21) xor sig_in(1) xor sig_in(0) = '1' then tmp(0) := '1'; end if; return tmp; end function; -- Return random integer in range [0..max-1] function rand_int(maxval : integer) return integer is variable r : unsigned(31 downto 0); variable x : integer; begin r := pseudo_rand(rand_val); x := to_integer(r(15 downto 0)) mod maxval; return x; end function; begin ------------------------------------------------------------------------------ -- Instantiate DUT ------------------------------------------------------------------------------ DUT : sda_fsoe_make generic map ( G_MAX_MTU => 1500, -- can be adjusted G_BRAM_DEPTH => 2048, G_DATA_WIDTH => 32, G_HEADER_WORDS => 1 ) port map ( clk => clk, reset => reset, -- AXI-Slave s_tvalid => s_tvalid, s_tready => s_tready, s_tdata => s_tdata, s_tkeep => s_tkeep, s_tlast => s_tlast, -- AXI-Master m_tvalid => m_tvalid, m_tready => m_tready, m_tdata => m_tdata, m_tkeep => m_tkeep, m_tlast => m_tlast ); ------------------------------------------------------------------------------ -- Clock Generation ------------------------------------------------------------------------------ clk_process : process begin clk <= '0'; wait for CLK_PERIOD/2; clk <= '1'; wait for CLK_PERIOD/2; end process; ------------------------------------------------------------------------------ -- Reset Process ------------------------------------------------------------------------------ reset_process : process begin reset <= '1'; wait for 10*CLK_PERIOD; reset <= '0'; wait; end process; ------------------------------------------------------------------------------ -- Stimulus: AXI-Slave Input (s_t*) ------------------------------------------------------------------------------ slave_stim : process(clk) variable cur_length : integer; variable cur_offset : integer; variable wait_gap : integer; begin if rising_edge(clk) then rand_val <= pseudo_rand(rand_val); -- Update seed case tb_send_state is when TB_SEND_IDLE => -- Have we sent all test packets? if pkt_index_in < NUM_TEST_PKTS then -- Random inter-packet gap wait_gap := rand_int(10); -- up to 9 cycles if wait_gap > 0 then s_tvalid <= '0'; s_tdata <= (others => '0'); s_tlast <= '0'; wait_gap := wait_gap - 1; else -- Start sending a new packet cur_length := rand_int(MAX_PKT_SIZE) + 1; -- Range 1..MAX_PKT_SIZE stored_pkt_size(pkt_index_in) <= cur_length; -- Create pseudo-random data for it for i in 0 to cur_length-1 loop stored_pkts(pkt_index_in)(8*i + 7 downto 8*i) <= std_logic_vector(to_unsigned(rand_int(256),8)); end loop; cur_offset := 0; s_tvalid <= '1'; tb_send_state <= TB_SEND_DATA; end if; else -- No more input packets s_tvalid <= '0'; s_tdata <= (others => '0'); end if; when TB_SEND_DATA => if s_tready = '1' then -- Output next 4 bytes from stored array s_tdata <= stored_pkts(pkt_index_in)(8*cur_offset+31 downto 8*cur_offset); cur_offset := cur_offset + 4; if (cur_offset*1 >= stored_pkt_size(pkt_index_in)) then -- Reached end of packet s_tlast <= '1'; s_tvalid <= '1'; tb_send_state <= TB_SEND_DONE; else s_tlast <= '0'; s_tvalid <= '1'; end if; end if; when TB_SEND_DONE => if s_tready = '1' then -- Final word accepted s_tvalid <= '0'; s_tlast <= '0'; pkt_index_in <= pkt_index_in + 1; tb_send_state <= TB_SEND_IDLE; end if; when others => tb_send_state <= TB_SEND_IDLE; end case; end if; end process; ------------------------------------------------------------------------------ -- AXI-Master Sink: Reading from m_t* interface ------------------------------------------------------------------------------ master_read : process(clk) begin if rising_edge(clk) then rand_val <= pseudo_rand(rand_val); -- random for backpressure -- random chance to deassert m_tready if rand_int(8) < 2 then m_tready <= '0'; -- ~25% chance of backpressure else m_tready <= '1'; end if; if m_tvalid = '1' and m_tready = '1' then -- Capture the output in scoreboard out_capture(out_capture_idx) <= m_tdata; out_capture_idx <= out_capture_idx + 1; -- If it's last if m_tlast = '1' then -- We can do checks, e.g. parse the header, compare data, etc. out_capture_idx <= out_capture_idx; -- freeze or finalize end if; end if; end if; end process; ------------------------------------------------------------------------------ -- Optional: Add checks / scoreboard logic in a separate process -- to compare header & data from out_capture vs stored_pkts ------------------------------------------------------------------------------ scoreboard_proc: process(clk) begin if rising_edge(clk) then -- Example: Once we've read enough data, we can parse the FSO header, check -- For brevity, the final check can be done offline or in a custom sub-block if pkt_index_in = NUM_TEST_PKTS then -- We could add a time limit or detect last out, then finish if out_capture_idx > 0 then -- End of simulation or checks report \"All packets submitted. End of test.\" severity note; wait for 100 ns; std.env.stop; -- end simulation end if; end if; end if; end process; end Behavioral;
- Comments
- Write a Comment Select to add a comment
To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.
Please login (on the right) if you already have an account on this platform.
Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: