Search code examples
vhdlfpgahdl

Simple VHDL testbench procedure for sending serial bytes?


I'm trying to remove bitbashing in my design and sending test signals from outside the DUT using a procedure. The format of the serialized message is a start bit of '0', the byte with MSB first, and a stop bit of '1'. The line idles at '1'. I think I'm having issue making use of datatypes to be passed between the procedure and the main process.

Here is my procedure (baudrate is a time constant for the period of the serial clock):

  procedure send_midi_byte (
    signal byte_in  : in  std_logic_vector;
    signal midi_out : out std_logic) is
  begin
    midi_out <= '0';
    wait for baudrate;
    for i in 7 to 0 loop
      midi_out <= byte_in(i);
      wait for baudrate;
    end loop;
    midi_out <= '1';
    wait for baudrate;
  end send_midi_byte;

And here is how I call it to send a few bytes (byte_slv is an 8 element std_logic_vector):

    byte_slv <= x"90";
    send_midi_byte(byte_slv, midi_in_int);

I have tried a few different methods and this is the only one that doesn't give an error, but of course it won't work because of the nonblocking assignments in the procedure meaning my serial signal will just be '1' for the length of time specified in baudrate.

How can I write this procedure correctly?


Solution

  • I have tried a few different methods and this is the only one that doesn't give an error, but of course it won't work because of the nonblocking assignments in the procedure meaning my serial signal will just be '1' for the length of time specified in baudrate.

    The premise of your assertion here is incorrect. The reason why the procedure call fails is the loop parameter specification provides a null range (the loop parameter is evaluated first, a loop statement executes 0 or more times).

    How can I write this procedure correctly?

    A Minimal, Complete, and Verifiable example with the fix included in procedure send_midi_byte:

    library ieee;
    use ieee.std_logic_1164.all;
    
    entity cmoc_cmoc is
    end entity;
    
    architecture mcve of cmoc_cmoc is
        constant baudrate:  time := 26.925 us;  -- 38.400 baud
        procedure send_midi_byte (
            signal byte_in:   in  std_logic_vector;
            signal midi_out:  out std_logic
        ) is
            alias in_byte: std_logic_vector (7 downto 0) is byte_in;  -- ADDED
        begin
            midi_out <= '0';
            wait for baudrate;
            for i in in_byte'range loop        -- WAS 7 to 0
                midi_out <= in_byte(i); -- WAS byte_in(i);
                wait for baudrate;
            end loop;
            midi_out <= '1';
            wait for baudrate;
        end procedure send_midi_byte;
        signal some_byte:   std_logic_vector (7 downto 0) := x"41";
        signal midi_out:    std_logic := '1';
        type baud is (IDLE,START, BD0, BD1, BD2, BD3, BD4, BD5, BD6, BD7, STOP);
        signal baud_cnt:    baud;
    begin
    PROCEDURE_CALL:
        process 
        begin
            wait for baudrate;  -- SHOW IDLE on midi_out;
            send_midi_byte(some_byte, midi_out);  -- added second parameter
            wait;
        end process;
    BAUD_CTR:
        process
        begin
            if baud_cnt = IDLE then
                wait until midi_out = '0';
            end if;
            loop
                baud_cnt <= baud'RIGHTOF(baud_cnt);
                wait for 0 ns;
                report "baud(" & baud'image(baud_cnt) &
                       ") midi_out = "  & std_ulogic'image(midi_out);
                wait for baudrate;
                if baud_cnt = STOP then
                    baud_cnt <= IDLE;
                    exit;
                end if;
            end loop;
            wait;
        end process;
    end architecture;
    

    Note the class of byte_in isn't provided in the procedure declaration. For a subprogram parameter of mode in the default class is constant.

    The MSB first order has been preserved to match your question text. (UARTs transmit the LSB first).

    The BAUD_CTR process is an embellishment to demonstrate transmitted baud order.

    For a UART with an input byte given as MSB on the left and transmitting the LSB first either the alias range can be reversed or the loop parameter could use `REVERSE_RANGE.

    The report statements:

    ghdl -r cmoc_cmoc --wave=cmoc_cmoc.ghw
    cmoc_cmoc.vhdl:45:13:@26925ns:(report note): baud(start) midi_out = '0'
    cmoc_cmoc.vhdl:45:13:@53850ns:(report note): baud(bd0) midi_out = '0'
    cmoc_cmoc.vhdl:45:13:@80775ns:(report note): baud(bd1) midi_out = '1'
    cmoc_cmoc.vhdl:45:13:@107700ns:(report note): baud(bd2) midi_out = '0'
    cmoc_cmoc.vhdl:45:13:@134625ns:(report note): baud(bd3) midi_out = '0'
    cmoc_cmoc.vhdl:45:13:@161550ns:(report note): baud(bd4) midi_out = '0'
    cmoc_cmoc.vhdl:45:13:@188475ns:(report note): baud(bd5) midi_out = '0'
    cmoc_cmoc.vhdl:45:13:@215400ns:(report note): baud(bd6) midi_out = '0'
    cmoc_cmoc.vhdl:45:13:@242325ns:(report note): baud(bd7) midi_out = '1'
    cmoc_cmoc.vhdl:45:13:@269250ns:(report note): baud(stop) midi_out = '1'
    

    And a waveform showing baud_cnt:

    MSB first order