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.)
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.
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:
severity failure
instead of severity error
, or find the option of your simulator that selects the severity level above which the simulation stops.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:
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).