1
votes

To learn VHDL, I'm implementing my own custom CPU with VHDL.

Tired of writing opcode bit pattern manually, I want to create very simple "assembler" to create bit pattern.

Here is current implemenation of such assembler:

library ieee;
use ieee.std_logic_1164.all;
use work.utility.all;

package assembler is
    function encode_opcode(cmd: string; re: integer; rd: integer; ra: integer; rb: integer; imm: integer) return std_logic_vector;
end;

package body assembler is
    function to_lower(x: character) return character is
        constant posA: integer := character'pos('A');
        constant posZ: integer := character'pos('Z');
        constant posLowerA: integer := character'pos('a');
        constant posX: integer := character'pos(x);
    begin
        if posA <= posX and posX <= posZ then
            return character'val(posX - posA + posLowerA);
        else
            return x;
        end if;
    end;

    function to_lower(x: string) return string is
        variable r: string(x'range);
    begin
        for i in x'range loop
            r(i) := to_lower(x(i));
        end loop;
        return r;
    end;

    function encode_cmd(cmd: string) return std_logic_vector is
        constant cmd2: string(1 to cmd'length) := cmd;
        variable cmd3: string(1 to 5) := "-----";
    begin
        if cmd'length > 5 then
            report "Illegal command: " & cmd severity error;
            return "";
        end if;
        for i in cmd2'range loop
            cmd3(i) := cmd2(i);
        end loop;
        case to_lower(cmd3) is
            -- Group 1: Memory access (omitted)
            -- Group 2: Addition
            when "addu-" => return "00100000";
            when "subu-" => return "00100001";
            when "add--" => return "00100010";
            when "sub--" => return "00100011";
            -- Group 3: Multiplication
            when "multu" => return "00110000";
            when "divu-" => return "00110001";
            when "mult-" => return "00110010";
            when "div--" => return "00110011";
            -- Group 4: Bitwise (omitted)
            -- Group 5: Shift
            when "shf--" => return "01010000";
            when "shfu-" => return "01010001";
            when "rot--" => return "01010010";
            -- Group 6: Branch absolute (omitted)
            -- Group 7: Branch relative (omitted)
            when others =>
                report "Illegal command: " & cmd severity error;
                return "";
        end case;
    end;

    function encode_opcode(cmd: string; re: integer; rd: integer; ra: integer; rb: integer; imm: integer) return std_logic_vector is
    begin
        return encode_cmd(cmd) & encode_unsigned(re, 5) & encode_unsigned(rd, 5) & encode_unsigned(ra, 5) & encode_unsigned(rb, 5) & encode_signed(imm, 4);
    end;
end;

Here is definition of encode_unsigned and encode_signed function:

function encode_unsigned(x: integer; n: integer) return std_logic_vector is
begin
    return std_logic_vector(to_unsigned(x, n));
end;

function encode_signed(x: integer; n: integer) return std_logic_vector is
begin
    return std_logic_vector(to_signed(x, n));
end;

Here is usage of encode_opcode function:

function rom_contents(addr: std_logic_vector) return std_logic_vector is
begin
    case decode_unsigned(addr) is
        when 0 => return encode_opcode("ADDu", 0, 1, 0, 0, 2);
        when 1 => return encode_opcode("ADD", 0, 2, 0, 0, -8);
        when 2 => return encode_signed(11, 32);
        when 3 => return encode_opcode("MULT", 0, 3, 1, 2, 0);
        when 6 => return encode_opcode("ADD", 0, 4, 3, 0, 0);
        when 8 => return encode_opcode("ADD", 0, 16, 0, 0, -8);
        when 9 => return encode_signed(16#12345678#, 32);
        when 11 => return encode_opcode("ROT", 31, 30, 16, 0, 4);
        when 12 => return encode_opcode("ROT", 29, 28, 30, 0, 4);
        when others => return (31 downto 0 => '0');
    end case;
end;

Whenever I make mistake and wrote something like encode_opcode("ROTE", 31, 30, 16, 0, 4);, I want to get error message like Illegal command: ROTE. However, it doesn't create any error message, and the length of return value of encode_opcode becomes 24 silently. (if there were no mistakes, lenght should be 32.)

Using enum for command would be too hard, as some of command looks like AND, XOR, GFu>, GT<=.

I'm using Quartus Prime Lite Edition version 18.0

UPDATE: usage of rom_contents:

entity instruction_memory_controller is
    port (
        clock: in std_logic;
        addr: in std_logic_vector(31 downto 0);
        q: out std_logic_vector(31 downto 0)
    );
end;

architecture RTL of instruction_memory_controller is
begin
    process(clock)
    begin
        if rising_edge(clock) then
            q <= rom_contents(addr);
        end if;
    end process;
end;

When I entered wrong command, q <= rom_contnts(addr) marked error due to mismatch size (24 vs 32).

When I changed return value of illegal command from "" to "XXXXXXXX", it compiles without any error/warning. (My expection is that it triggers Illegal command error by report statement.)

1
The normal case statement doesn't support don't-care s. You need to use the VHDL-2008 case? statement. More info here. However "rote" is four chars long, while you are checking for five chars. That will not work. You should switch to if-elsif statements (and use the matching equality operator ?= for the don't care s).JHBonarius
@JHBonarius :: The first few lines of encode_cmd makes "normalize" command string to 5-length lower-case string padded with '-' character. Here, '-' is not "don't care" std_logic_vector signal; it is character whicn consists string.Venusaur
@Venusaur: congratulations for you first steps in VHDL! I wish all VHDL beginners had such a good understanding and advanced use of the language.Renaud Pacalet
@Venusaur: compilation is not simulation. The error should be raised at simulation, not compilation.Renaud Pacalet
@RenaudPacalet 1) When I put report statement inside encode_unsigned and encode_signed function, (almost) all of my entities reports that message during Analysis & Synthesis stage, except this instruction_memory_controller entity. 2) It seems that Quartus runs simulation with logic-cell-level implementation; In this case, I can't expect my error message in simulation stage.Venusaur

1 Answers

1
votes
  1. On the length of the returned vectors: your encode_cmd function returns an empty vector when an illegal opcode is encountered. So, the total length of the vector returned by encode_opcode is not 32 but 24... Return "00000000" or any other 8-bits long vector if you want 32 bits results in all cases.

  2. About the missing error message: It shows up with Modelsim. As you do not show what you do with rom_contents we cannot guess whether the vector length mismatch will raise a simulation error or not. If it does, could it be that with Quartus it takes precedence over the report "Illegal command... and that the latter is never fired? Or could it be that you missed the error message because it was not the last printed one?

Notes:

  • If you want the simulation to really stop when an illegal opcode is encountered try maybe severity failure instead of severity error, or find the option of your simulator that selects the severity level above which the simulation stops.
  • You use std_logic and std_logic_vector which are resolved types. Considering your design I doubt that you really have multiple drive situations. You should, maybe consider std_ulogic and std_ulogic_vector, instead, as it would be safer. And as you apparently use the VHDL 2008 flavor, it is the same with signed and unsigned that you could substitute by u_signed and u_unsigned. All in all it would bring the benefit that unwanted multiple drive situations would raise errors.

EDITS after comments have been exchanged:

Quartus is a synthesizer. So, there is a chance that it synthesizes your design before simulating it at gate level. If this is true then you must realize something important: report statements are ignored by the synthesis process because they have no hardware equivalent. So, the best you can expect with Quartus is that it detects that a report statement is always reached and decides to show it during the synthesis process. This is possible in some cases because a synthesizer tries to simplify the design by propagating constants:

constant condition: boolean := true;
...
if condition then
    report 'Foo bar';
end if;

becomes:

report 'Foo bar';

Unfortunately, in more complex examples, like yours for instance, it is far more difficult to detect that a report statement is always reached. This is because it is reached only after a given number of clock cycles, when your address reaches the faulty ROM entry. Don't expect a logic synthesizer to detect this.

Solutions:

  1. Use a real simulator, like GHDL or Modelsim, for instance, to validate your design.
  2. Implement a real error signal, instead of an abstract, simulation-oriented, report statement. And monitor this signal during post-synthesis simulation (and during execution on your hardware target). This will be the first step toward a real invalid instruction exception of your processor.

Note about VHDL 2008: if you use the '-' (don't care) value in case statements as a way to match any value, then you are using VHDL 2008, I think. It has been introduced in 2008, if I remember well. Before that it was a value like any other value (except in the ieee.numeric_std.std_match function).