I have a very simple I2C bit-banging library for ATTINY85.
#define PORT_SDA PB0
#define PORT_SCL PB2
#define SIGNAL_HIGH(PORT) PORTB |= ( 1 << PORT )
#define SIGNAL_LOW(PORT) PORTB &= ~( 1 << PORT )
void LED_ON(void);
void LED_OFF(void);
void i2c_init(void);
void i2c_start(void);
void i2c_read(void);
void i2c_stop(void);
void i2c_write(uint8_t byte);
void i2c_init()
{
DDRB |= ( 1 << PORT_SDA );
DDRB |= ( 1 << PORT_SCL );
}
void LED_ON( void )
{
PORTB |= 0b00000010;
}
void LED_OFF( void )
{
PORTB &= 0b111111101;
}
void i2c_start( void )
{
SIGNAL_HIGH( PORT_SCL );
SIGNAL_HIGH( PORT_SDA );
SIGNAL_LOW( PORT_SDA );
SIGNAL_LOW( PORT_SCL );
}
void i2c_stop( void )
{
SIGNAL_LOW( PORT_SCL );
SIGNAL_LOW( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
SIGNAL_HIGH( PORT_SDA );
}
void i2c_write(uint8_t byte)
{
uint8_t bit;
for ( bit = 0; bit < 0x08; bit++ )
{
if( ( byte << bit ) & 0x80 )
SIGNAL_HIGH( PORT_SDA );
else
SIGNAL_LOW( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
SIGNAL_LOW( PORT_SCL );
}
SIGNAL_HIGH( PORT_SDA );
SIGNAL_HIGH( PORT_SCL );
SIGNAL_LOW( PORT_SCL );
}
I'm able to successfully write to I2C without any problems. I have tested this code with SSD1306 and LC2404B and everything works great even the timing if VCC is set to 4.2V.
i2c_init();
i2c_start();
i2c_write( 0xA0 );
i2c_write( 0x01 );
i2c_write( 0x13 );
i2c_stop();
While writting works perfectly, I can't seem to be able to trigger any of my I2C modules with ATTINY85 to return me a value that I can read later.
I connected raspberry and GY-521 sensor (since it returns value even if internal address is not set). I can detect the sensor and read a value from it with raspberry the following way:
i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
i2cget -y 1 0x68
0x02
This is the output on the oscilloscope:
I can see the sensor data changing. The problem is I can't seem to replicate the same request from ATTINY85 to the sensor. The sensor simply doesn't respond with the value.
I can't seem to understand if the last bit in the first byte is an ACK or 'READ' indicator bit, so I tried different addresses.
i2c_start();
i2c_write( 0b11010001 ); // address: 0xD1
i2c_start();
i2c_write( 0b11010000 ); // address: 0xD0
i2c_start();
i2c_write( 0b10100001 ); // address: 0xA1
i2c_start();
i2c_write( 0b10100000 ); // address: 0xA0
but regardless on what address I use, the sensor simply doesn't respond (in the oscioloscope) and the SDA line stays high after I send the address byte. I also tried to append another start() condition after I sent the address, but still no luck. Any hints on where I'm going wrong? I simply want to make the sensor respond to the ATTINY85 read request so I can later read the value. Thanks!
I'd suggest reading a couple tutorials on I2C to get a general understanding of the protocol. See https://learn.sparkfun.com/tutorials/i2c for example.
In short, I2C is a two-wire multi-master bus with a clock line and a data line. In any communication, whether it is a read or a write, the master supplies the clock signal. Your i2c_write() implements this with the SCL transitions.
In order to read a value back, you need to also supply the clock for that the slave uses to output data. No clock, no data. So, you need to implement an i2c_read() similar to your i2c write, that generates the clock transitions, and shifts in each bit one at a time.
You need to let the SDA line be an input so you can detect whether the slave is sending an ACK or not. You don't have any code to set the SDA line to be an input so there is no way the slave device could send any data back to you; the value you are putting on the SDA line might overpower whatever the slave is trying to do. And to read data back, you need to make an i2c_read function that wiggles the SCL line while SDA is an input. Your I2C implementation is therefore far from complete. You could carefully read the I2C spec from NXP or look for a more complete bit-banging I2C implementation to use as a reference.
Related
In my architecture I define a signal of some record type. Each of the record elements is driven in one process only.
The whole record signal is passed to procedures. The procedures will later be placed in a package. Passing the record as type "inout" leeds to all accessed record elements resolving to 'X'.
The code runs in Modelsim and is only for verification (no synthesis needed).
Driving the record elements directly works.
Minimal example; Architecture header:
-- Record type
type tr_Data is record
r1_A : std_logic;
r1_B : std_logic;
end record;
-- Signal definition and initialization
signal sr_Data : tr_Data := (
r1_A => '0',
r1_B => '1'
);
Architecture body:
-- This process only modifies sr_Data.r1_A
prcss_A: process
procedure proc_Modify_A(signal d: inout tr_Data) is
begin
d.r1_A <= not d.r1_A;
end procedure;
begin
wait for 1 us;
--sr_Data.r1_A <= not sr_Data.r1_A; -- This works
proc_Modify_A(sr_Data); -- This works not
wait for 1 us;
wait;
end process;
-- This process only modifies sr_Data.r1_B
prcss_B: process
procedure proc_Modify_B(signal d: inout tr_Data) is
begin
d.r1_B <= not d.r1_B;
end procedure;
begin
wait for 1.5 us;
--sr_Data.r1_B <= not sr_Data.r1_B; -- This works
proc_Modify_B(sr_Data); -- This works not
wait for 1 us;
wait;
end process;
This is the Result in Modelsim:
I'm not sure, if this is possible at all. Maybe there are also better solutions to my problem.
Thank's for the help!
This question demonstrates why providing a minimal, complete, and verifiable exampe is desirable for code errors (instead of snippets as here).
Creating a MCVe:
library ieee;
use ieee.std_logic_1164.all;
entity record_process is
end entity;
architecture foo of record_process is
type tr_Data is record
r1_A: std_logic;
r1_B: std_logic;
end record;
signal sr_Data: tr_Data := (r1_A => '0', r1_B => '1');
begin
prcss_A: -- This process only modifies sr_Data.r1_A
process
procedure proc_Modify_A(signal d: inout tr_Data) is
begin
d.r1_A <= not d.r1_A;
end procedure;
begin
wait for 1 us;
-- sr_Data.r1_A <= not sr_Data.r1_A; -- This works
proc_Modify_A(sr_Data); -- This works not
wait for 1 us;
wait;
end process;
prcss_B: -- This process only modifies sr_Data.r1_B
process
procedure proc_Modify_B(signal d: inout tr_Data) is
begin
d.r1_B <= not d.r1_B;
end procedure;
begin
wait for 1.5 us;
--sr_Data.r1_B <= not sr_Data.r1_B; -- This works
proc_Modify_B(sr_Data); -- This works not
wait for 1 us;
wait;
end process;
MONITOR:
process (sr_Data)
begin
report
LF & HT &
"sr_Data.r1_A = " & std_logic'image(sr_Data.r1_A) &
LF & HT &
"sr_Data.r1_B = " & std_logic'image(sr_Data.r1_B);
end process;
end architecture;
we see reported behavior that matches the OP's waveform:
record_process.vhdl:44:9:#0ms:(report note):
sr_Data.r1_A = '0'
sr_Data.r1_B = '1'
record_process.vhdl:44:9:#1us:(report note):
sr_Data.r1_A = 'X'
sr_Data.r1_B = '1'
record_process.vhdl:44:9:#1500ns:(report note):
sr_Data.r1_A = 'X'
sr_Data.r1_B = 'X'
The 'X's are caused by the way drivers are associated with subprogram actual parameters:
IEEE Std 1076-2008
4.2 Subprogram declarations
4.2.2.3 Signal parameters
A process statement contains a driver for each actual signal associated with a formal signal parameter of mode out or inout in a subprogram call. Similarly, a subprogram contains a driver for each formal signal parameter of mode out or inout declared in its subprogram specification.
For a signal parameter of mode inout or out, the driver of an actual signal is associated with the corresponding driver of the formal signal parameter at the start of each call. Thereafter, during the execution of the subprogram body, an assignment to the driver of a formal signal parameter is equivalent to an assignment to the driver of the actual signal.
10.7 Procedure call statement
For each formal parameter of a procedure, a procedure call shall specify exactly one corresponding actual parameter. This actual parameter is specified either explicitly, by an association element (other than the actual open) in the association list or, in the absence of such an association element, by a default expression (see 6.5.2).
There's a driver for the actual associated in whole, here a signal of a record type.
(This also tells you Tricky's answer is valid, which reduces the actual to an element of the whole.)
There's another possible solution
Drop the parameter list:
architecture fum of record_process is
type tr_Data is record
r1_A: std_logic;
r1_B: std_logic;
end record;
signal sr_Data: tr_Data := (r1_A => '0', r1_B => '1');
begin
prcss_A: -- This process only modifies sr_Data.r1_A
process
procedure proc_Modify_A is
begin
sr_Data.r1_A <= not sr_Data.r1_A;
end procedure;
begin
wait for 1 us;
-- sr_Data.r1_A <= not sr_Data.r1_A; -- This works
proc_Modify_A; -- This works not
wait for 1 us;
wait;
end process;
prcss_B: -- This process only modifies sr_Data.r1_B
process
procedure proc_Modify_B is
begin
sr_Data.r1_B <= not sr_Data.r1_B;
end procedure;
begin
wait for 1.5 us;
--sr_Data.r1_B <= not sr_Data.r1_B; -- This works
proc_Modify_B; -- This works not
wait for 1 us;
wait;
end process;
MONITOR:
process (sr_Data)
begin
report
LF & HT &
"sr_Data.r1_A = " & std_logic'image(sr_Data.r1_A) &
LF & HT &
"sr_Data.r1_B = " & std_logic'image(sr_Data.r1_B);
end process;
end architecture;
which produces:
record_process.vhdl:89:9:#0ms:(report note):
sr_Data.r1_A = '0'
sr_Data.r1_B = '1'
record_process.vhdl:89:9:#1us:(report note):
sr_Data.r1_A = '1'
sr_Data.r1_B = '1'
record_process.vhdl:89:9:#1500ns:(report note):
sr_Data.r1_A = '1'
sr_Data.r1_B = '0'
without any driver conflicts, lacking multiple drivers (14.7.2 Drivers) requiring resolution (14.7.3.4 Signal update) during simulation.
14.7.2 Drivers
Every signal assignment statement in a process statement defines a set of drivers for certain scalar signals. There is a single driver for a given scalar signal S in a process statement, provided that there is at least one signal assignment statement in that process statement and that the longest static prefix of the target signal of that signal assignment statement denotes S or denotes a composite signal of which S is a subelement. Each such signal assignment statement is said to be associated with that driver. Execution of a signal assignment statement affects only the associated driver(s).
The longest static prefix is defined in 8. Names:
8.1 General
A static signal name is a static name that denotes a signal. The longest static prefix of a signal name is the name itself, if the name is a static signal name; otherwise, it is the longest prefix of the name that is a static signal name. ...
where the longest static prefix now includes record elements as opposed to actual signals of a record type associated in whole.
This is possible because a subprogram is a sequence of statements (4.2 Subprogram bodies) and the procedures are declared within the scope of the declaration of sr_Data (12.1 Declarative region, 12.2 Scope of declarations, 12.3 Visibility).
The error is here because the procedures take in a record type. When this happens, they create a driver on the entire record, not just the elements driven internally. Hence you have multiple drivers because the same signal is driven from multiple processes.
If you want to drive only the elements, make the procedures take an inout of std_logic type and only pass in the signal record elements. This way, each process is only driving a single element of the record.
-- This process only modifies sr_Data.r1_A
prcss_A: process
procedure proc_Modify_A(signal d: inout std_logic) is
begin
d <= not d;
end procedure;
begin
wait for 1 us;
--sr_Data.r1_A <= not sr_Data.r1_A; -- This works
proc_Modify_A(sr_Data.r1_A); -- This works not
wait for 1 us;
wait;
end process;
-- This process only modifies sr_Data.r1_B
prcss_B: process
procedure proc_Modify_B(signal d: inout std_logic) is
begin
d <= not d;
end procedure;
begin
wait for 1.5 us;
--sr_Data.r1_B <= not sr_Data.r1_B; -- This works
proc_Modify_B(sr_Data.r1_B); -- This works not
wait for 1 us;
wait;
end process;
No time now for an explanation but the following solution should achieve exactly what you are asking for:
Architecture header:
-- Record type
type tr_Data is record
r1_A : std_logic;
r1_B : std_logic;
end record;
constant TR_DATA_INIT : tr_Data := (
r1_A => 'Z',
r1_B => 'Z'
);
-- Signal definition and initialization
signal sr_Data : tr_Data := (
r1_A => '0',
r1_B => '1'
);
Architecture body:
-- This process only modifies sr_Data.r1_A
prcss_A: process
procedure proc_keep_A(signal d: inout tr_Data) is
begin
d <= TR_DATA_INIT;
d.r1_A <= d.r1_A;
end procedure;
procedure proc_Modify_A(signal d: inout tr_Data) is
begin
d.r1_A <= not d.r1_A;
end procedure;
begin
proc_keep_A(sr_Data); -- needed only at the very beginning of the process
wait for 1 us;
--sr_Data.r1_A <= not sr_Data.r1_A; -- This works
proc_Modify_A(sr_Data); -- This works too
wait for 1 us;
wait;
end process;
-- This process only modifies sr_Data.r1_B
prcss_B: process
procedure proc_keep_B(signal d: inout tr_Data) is
begin
d <= TR_DATA_INIT;
d.r1_B <= d.r1_B;
end procedure;
procedure proc_Modify_B(signal d: inout tr_Data) is
begin
d.r1_B <= not d.r1_B;
end procedure;
begin
proc_keep_B(sr_Data); -- needed only at the very beginning of the process
wait for 1.5 us;
--sr_Data.r1_B <= not sr_Data.r1_B; -- This works
proc_Modify_B(sr_Data); -- This works too
wait for 1 us;
wait;
end process;
Thanks for all the detailed informations. I learned a lot through that.
Based on your answers I found a solution. For my purpose, it is to use a resolved type and init it at the beginning of each process as Giampietro Seu suggested. This also enables me to put non-resolved types like integer, time etc. in the record.
Here is the full reproducable minimal example.
Entity:
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use work.pkg_Minimal.all;
entity eMinimalExample is
end entity;
architecture aMinimal of eMinimalExample is
-- Signal of resolved subtype
signal sr_Data : rtr_Data;
begin
-- This process only modifies sr_Data.r1_A / ri_A
prcss_A: process
begin
proc_Init_A(sr_Data); -- Only needed once
wait for 1 us;
proc_Modify_A(sr_Data);
wait for 1 us;
wait;
end process;
-- This process only modifies sr_Data.r1_B / ri_B
prcss_B: process
begin
proc_Init_B(sr_Data); -- Only needed once
wait for 1.5 us;
proc_Modify_B(sr_Data);
wait for 1 us;
wait;
end process;
prcss_Monitor: process (sr_Data)
begin
report
LF & HT &
"sr_Data.r1_A = " & std_logic'image(sr_Data.r1_A) &
LF & HT &
"sr_Data.r1_B = " & std_logic'image(sr_Data.r1_B) &
LF & HT &
"sr_Data.ri_A = " & integer'image(sr_Data.ri_A) &
LF & HT &
"sr_Data.ri_B = " & integer'image(sr_Data.ri_B);
end process;
end architecture;
Package:
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
package pkg_Minimal is
-- Record type
type tr_Data is record
r1_A : std_logic;
ri_A : integer;
r1_B : std_logic;
ri_B : integer;
end record;
-- Array of record
type ta_Data is array(natural range <>) of tr_Data;
-- Resolution function for record
function f_resolve_Data (ca_Data: in ta_Data) return tr_Data;
-- Resolved record subtype
subtype rtr_Data is f_resolve_Data tr_Data;
-- Init A with init values and B with recessive values
procedure proc_Init_A (signal d: inout rtr_Data);
-- Init B with init values and A with recessive values
procedure proc_Init_B (signal d: inout rtr_Data);
-- Modify A only
procedure proc_Modify_A (signal d: inout rtr_Data);
-- Modify B only
procedure proc_Modify_B (signal d: inout rtr_Data);
end package;
package body pkg_Minimal is
-- Record init values
constant cr_Data_Init : tr_Data := (
r1_A => '0',
ri_A => 10,
r1_B => '1',
ri_B => 20
);
-- Recessive values for resolution function
constant cr_Data_Recessive : tr_Data := (
r1_A => 'Z',
ri_A => integer'low, -- Lowest value is burned,
r1_B => 'Z', -- we can't use it anymore
ri_B => integer'low
);
-- Resolution function for record
function f_resolve_Data (constant ca_Data: in ta_Data) return tr_Data is
variable vr_Data : tr_Data := cr_Data_Recessive;
begin
for i in ca_Data'range loop
if cr_Data_Recessive.r1_A /= ca_Data(i).r1_A then
vr_Data.r1_A := ca_Data(i).r1_A;
end if;
if cr_Data_Recessive.r1_B /= ca_Data(i).r1_B then
vr_Data.r1_B := ca_Data(i).r1_B;
end if;
if cr_Data_Recessive.ri_A /= ca_Data(i).ri_A then
vr_Data.ri_A := ca_Data(i).ri_A;
end if;
if cr_Data_Recessive.ri_B /= ca_Data(i).ri_B then
vr_Data.ri_B := ca_Data(i).ri_B;
end if;
end loop;
return vr_Data;
end function;
procedure proc_Init_A(signal d: inout rtr_Data) is
begin
d <= cr_Data_Recessive;
d.r1_A <= cr_Data_Init.r1_A;
d.ri_A <= cr_Data_Init.ri_A;
end procedure;
procedure proc_Init_B(signal d: inout rtr_Data) is
begin
d <= cr_Data_Recessive;
d.r1_B <= cr_Data_Init.r1_B;
d.ri_B <= cr_Data_Init.ri_B;
end procedure;
procedure proc_Modify_A(signal d: inout rtr_Data) is
begin
d.r1_A <= not d.r1_A;
d.ri_A <= d.ri_A + 1;
end procedure;
procedure proc_Modify_B(signal d: inout rtr_Data) is
begin
d.r1_B <= not d.r1_B;
d.ri_B <= d.ri_B + 1;
end procedure;
end package body;
Output:
** Note:
sr_Data.r1_A = '0'
sr_Data.r1_B = '1'
sr_Data.ri_A = 10
sr_Data.ri_B = 20
Time: 0 ps Iteration: 0 Instance: /eminimalexample
** Note:
sr_Data.r1_A = '1'
sr_Data.r1_B = '1'
sr_Data.ri_A = 11
sr_Data.ri_B = 20
Time: 1 us Iteration: 1 Instance: /eminimalexample
** Note:
sr_Data.r1_A = '1'
sr_Data.r1_B = '0'
sr_Data.ri_A = 11
sr_Data.ri_B = 21
Time: 1500 ns Iteration: 1 Instance: /eminimalexample
I installed the GNAT-GPS and the AVR-ELF 3 days ago to play with. I got a blinky going and thought I might play around some more. I have no non-VHDL Ada experience.
Here's the scenario I have working in C:
I have it set up so that using a GPIO typedef, I can refer to all the information necessary to set up an GPIO pin (i.e. pin number, pin reg address, dd reg address and port reg address). Then I do the same for, say LED0, so that logically I can connect LED0 to GPIO15, which is itself connected to PB1 of the AVR microcontroller.
I try to do the same in Ada. I feel like I might be writing C in Ada; feel free to let me know afterwards if there's a better way to do this in Ada.
I set up the AVR registers for a particular pin to connect to its short name reference:
-- PB1
PB1_Port_reg : Unsigned_8;
PB1_Dd_reg : Unsigned_8;
PB1_Pin_reg : Unsigned_8;
for PB1_Port_reg'Address use AVR.Atmega328p.PORTB'Address;
for PB1_Dd_reg'Address use AVR.Atmega328p.DDRB'Address;
for PB1_Pin_reg'Address use AVR.Atmega328p.PINB'Address;
PB1_Pin : constant := 1;
Then I setup its short name reference to connect to its package pin number:
-- ATmega328p DIP28 Pin15 is PB1
Pin15_Port_reg : Unsigned_8;
Pin15_Dd_reg : Unsigned_8;
Pin15_Pin_reg : Unsigned_8;
for Pin15_Port_reg'Address use PB1_Port_reg'Address;
for Pin15_Dd_reg'Address use PB1_Dd_reg'Address;
for Pin15_Pin_reg'Address use PB1_Pin_reg'Address;
Pin15_Pin : constant := PB1_Pin;
Next I define a record to hold all the parameters for the pin together:
type gpio_t is record
pin : Unsigned_8;
pin_reg : Unsigned_8;
dd_reg : Unsigned_8;
port_reg : Unsigned_8;
end record;
This is to allow me to write the following function:
procedure gpio_map (gpio_t_dest : in out gpio_t; gpio_t_pin, gpio_t_pin_reg, gpio_t_dd_reg, gpio_t_port_reg : in Unsigned_8) is
begin
gpio_t_dest.pin := gpio_t_pin;
gpio_t_dest.pin_reg := gpio_t_pin_reg;
gpio_t_dest.dd_reg := gpio_t_dd_reg;
gpio_t_dest.port_reg := gpio_t_port_reg;
end gpio_map;
In the future, I'll be looking to have it as:
procedure gpio_map_future (gpio_t_dest : in out gpio_t; gpio_t_src : in gpio_t) is
begin
gpio_t_dest.pin := gpio_t_src.pin;
gpio_t_dest.pin_reg := gpio_t_src.pin_reg;
gpio_t_dest.dd_reg := gpio_t_src.dd_reg;
gpio_t_dest.port_reg := gpio_t_src.port_reg;
end gpio_map;
This gpio_map function is used to connect a package pin gpio_t to a package pin number:
gpio_map(gpio15, Pin15_pin, Pin15_pin_reg, Pin15_dd_reg, Pin15_port_reg);
I find that the LED is correctly initialized if I use this function:
core_reg_write(Pin15_dd_reg, Shift_Left(1,Integer(Pin15_pin))); -- works
But is not correctly initialized if I do:
core_reg_write(gpio15.dd_reg, Shift_Left(1,Integer(gpio15.pin))); -- does not work
This, however, works:
core_reg_write(Pin15_dd_reg, Shift_Left(1,Integer(gpio15.pin))); -- works
It is clear to me that I have
Pin15_pin = 1 # address (don't care - a variable)
Pin15_pin_reg = (don't care) # address 0x23
Pin15_dd_reg = (0b00000000) # address 0x24
Pin15_port_reg = (don't care) # address 0x25
And that
gpio15.pin = 1 # address (don't care, but not same as Pin15_pin address)
gpio15.pin_reg = (don't care) # address IS NOT 0x23
gpio15.dd_reg = (don't care) # address IS NOT 0x24
gpio15.port_reg = (don't care) # address IS NOT 0x25
How do I maintain fixed memory addresses for record members, i.e., get
gpio15.pin_reg = (don't care) # address 0x23
gpio15.dd_reg = (don't care) # address 0x24
gpio15.port_reg = (don't care) # address 0x25
And even better if I can also get
gpio15.pin = 1 # address (same as Pin15_pin address)
Sorry for the long question; hoping it helped make it clear.
You can't really get what you want via assignment of the two types. All that does is copy the current values, not the register addresses. Here is an option:
Create a type similar to your gpio_t type but make it exactly match the register map for your micro. That means you won't be storing the pin number in it and you need to include all the surrounding registers. Here is an example I found from another file for a different micro, but hopefully serves as an example
type Register_Layout is limited record
DIR : Unsigned_32;
DIRCLR : Unsigned_32;
DIRSET : Unsigned_32;
DIRTGL : Unsigned_32;
OUTVAL : Unsigned_32;
OUTCLR : Unsigned_32;
OUTSET : Unsigned_32;
OUTTGL : Unsigned_32;
INPUT : Unsigned_32;
CTRL : Unsigned_32;
WRCONFIG : Unsigned_32;
EVCTRL : Unsigned_32;
end record
with
Pack,
Volatile,
Size => 12*32;
The record type should be limited so that you ensure it is passed by reference and not by copy.
Note: You can also use a representation clause to provide the byte and bit layout of the structure instead. It will depend on the compiler that you use.
Once you have your micro's registers laid out to match the datasheet, you then create a variable and map that to the address you want, just like you did with the individual variables
Register_B : Register_Layout with
Address => System'To_Address(Some_Address),
Volatile => True,
Import => True;
This will map the entire record variable to that address.
After that, you need to modify your function calls to take the whole record as a parameter instead of the just the register. As an example:
Core_Reg_Write_DIR(Register_B, Shift_Left(1,Integer(PB1_Pin)));
If you need to have things be more fancy and have the right registers and mask value selected via pin, then you either need to use
CASE statements
Arrays of access types/addresses (using the pin type as the index).
A way to calculate the register address and mask from the pin and use that on a locally declared variable's address attribute inside a function call using a pin as a parameter.
You can't really have individual record components addressed differently (this is true in C and C++ as well).
Ok, after looking at your example, I came up with a similar solution in Ada. That said, I don't really care for how exposed access types are here. I'll leave my previous answer since I feel using records directly is a better method overall, but to specifically answer your question, here is an example I tested out in GNAT GPL 2017 using a handmade runtime (for another chip, but it was enough to verify compilation). Trying to compile it in a non embedded version of GNAT met with compiler crashes (I am assuming because the addresses were bad for windows). Hopefully this gives an example that better fits your personal requirements
registers.ads
with Interfaces;
-- Basic Register type and functionality
package Registers with Pure is
type Register is limited private;
type Register_Access is access all Register with Storage_Size => 0;
procedure Core_Reg_Write
(Target : not null Register_Access;
Value : Interfaces.Unsigned_8)
with Inline;
function Core_Reg_Read
(Source : not null Register_Access)
return Interfaces.Unsigned_8
with Inline;
private
type Register is limited record
Value : Interfaces.Unsigned_8;
end record
with Volatile, Size => 8;
end Registers;
registers.adb
package body Registers is
procedure Core_Reg_Write
(Target : not null Register_Access;
Value : Interfaces.Unsigned_8)
is begin
Target.Value := Value;
end Core_Reg_Write;
function Core_Reg_Read
(Source : not null Register_Access)
return Interfaces.Unsigned_8
is begin
return Source.Value;
end Core_Reg_Read;
end Registers;
io_registers.ads
with Registers;
-- Specific Register types and functionality
package IO_Registers with Pure is
-- Use different ones for each register to avoid accidental copy/paste
-- errors.
type Port_Register is new Registers.Register_Access;
type DD_Register is new Registers.Register_Access;
type Pin_Register is new Registers.Register_Access;
type Pin_Number is new Positive range 1 .. 8;
type GPIO_Register is record
Port_Reg : Port_Register;
DD_Reg : DD_Register;
Pin_Reg : Pin_Register;
Pin : Pin_Number;
end record;
end IO_Registers;
predefined_registers.ads
with Registers;
with System;
package Predefined_Registers is
-- Fake addresses here, since I don't have your atmega package
GPIO_15_Pin_Reg : aliased Registers.Register
with
Address => System'To_Address(16#80000400#),
Volatile,
Convention => C,
Import;
GPIO_15_DD_Reg : aliased Registers.Register
with
Address => System'To_Address(16#80000401#),
Volatile,
Convention => C,
Import;
GPIO_15_Port_Reg : aliased Registers.Register
with
Address => System'To_Address(16#80000402#),
Volatile,
Convention => C,
Import;
GPIO_15_Pin : constant := 1;
end Predefined_Registers;
program.adb
with IO_Registers;
with Predefined_Registers;
procedure Program is
GPIO_15 : IO_Registers.GPIO_Register :=
(Port_Reg => Predefined_Registers.GPIO_15_Port_Reg'Access,
Pin_Reg => Predefined_Registers.GPIO_15_Pin_Reg'Access,
DD_Reg => Predefined_Registers.GPIO_15_DD_Reg'Access,
Pin => Predefined_Registers.GPIO_15_Pin);
begin
-- Notice the use of IO_Registers for this call. The new types were
-- created there, so the corresponding ops were too
IO_Registers.Core_Reg_Write(GPIO_15.Port_Reg,16#01#);
end Program;
After a bit of thought, I decided to follow on what I already do in C. There, I have the following typedef defined
typedef struct {
IO_REG_TypeDef_t portr;
IO_REG_TypeDef_t ddr;
IO_REG_TypeDef_t pinr;
volatile uint8_t pin;
} GPIO_TypeDef_t;
And IO_REG_t is itself defined as
typedef struct {
volatile uint8_t* io_reg;
} IO_REG_TypeDef_t;
So clearly the key parameters for the gpio are lugged around in a typedef. I thought to do the same in Ada. Again, forgive me if I am speaking C in Ada; feel free to suggest more Ada-standard approaches.
I define the gpio pin components:
-- GPIO15 is PB1 on ATmega328p 28 DIP
gpio15_pin_reg : Unsigned_8;
for gpio15_pin_reg'Address use Atmega328p.PINB'Address;
gpio15_dd_reg : Unsigned_8;
for gpio15_dd_reg'Address use Atmega328p.DDRB'Address;
gpio15_port_reg : Unsigned_8;
for gpio15_port_reg'Address use Atmega328p.PORTB'Address;
gpio15_pin : constant Unsigned_8 := 1;
Register read & write functions are defined:
procedure core_reg_write (reg: in out Unsigned_8; value: in Unsigned_8) is
begin
reg := value;
end core_reg_write;
function core_reg_read (reg: in Unsigned_8) return Unsigned_8 is
value : Unsigned_8;
begin
value := reg;
return value;
end core_reg_read;
Then a record is defined, this time, to lug around the pin variable and, instead of variables for the pin, dd and port registers, their addresses instead:
type gpio_t is record
pin : Unsigned_8;
pin_reg_addr : System.Address;
dd_reg_addr : System.Address;
port_reg_addr : System.Address;
end record;
The record for a given gpio pin is assembled:
gpio15 : gpio_t := (gpio15_pin, gpio15_pin_reg'Address, gpio15_dd_reg'Address, gpio15_port_reg'Address);
Procedures that take this record and set parameters of the pin are defined:
procedure gpio_output (gpio : in gpio_t) is
dd_reg : Unsigned_8;
for dd_reg'Address use gpio.dd_reg_addr;
begin
core_reg_write(dd_reg, core_reg_read(dd_reg) or shift_left(1,integer(gpio.pin)));
end gpio_output;
procedure gpio_hi (gpio : in gpio_t) is
port_reg : Unsigned_8;
for port_reg'Address use gpio.port_reg_addr;
begin
core_reg_write(port_reg, core_reg_read(port_reg) or shift_left(1,integer(gpio.pin)));
end gpio_hi;
procedure gpio_lo (gpio : in gpio_t) is
port_reg : Unsigned_8;
for port_reg'Address use gpio.port_reg_addr;
begin
core_reg_write(port_reg, core_reg_read(port_reg) and not shift_left(1,integer(gpio.pin)));
end gpio_lo;
In each of these procedures, the required registers are, for lack of a better description, manually dereferenced.
The following sequence follows the begin keyword:
-- Initialize
gpio_output(gpio15);
For_loop_0:
loop
-- turn on
gpio_hi(gpio15);
-- loop
Lazy_delay_1:
for I in Unsigned_32 range 0 .. 100_000 loop
null;
end loop Lazy_delay_1;
-- turn off
gpio_lo(gpio15);
-- loop
Lazy_delay_2:
for I in Unsigned_32 range 0 .. 100_000 loop
null;
end loop Lazy_delay_2;
end loop For_loop_0;
And the led blinks.
This achieves what I want but I’m open to other approaches that take a composite gpio_t-like type and don’t require manual dereferencing of the address/pointer.
After playing around a bit, in this online compiler (https://www.tutorialspoint.com/compile_ada_online.php) I got this working:
with Ada.Text_IO; use Ada.Text_IO;
with Interfaces; use Interfaces;
with System; use System;
procedure Hello is
-- pseudo hardware registers, unknown addresses, known contents
temp0 : interfaces.unsigned_8 := 2#00000101#; -- pinr
temp1 : interfaces.unsigned_8 := 2#10000000#; -- ddr
temp2 : interfaces.unsigned_8 := 2#10000000#; -- portr
-- core
type io_reg_t is limited record
io_reg : interfaces.unsigned_8;
end record;
pragma volatile(io_reg_t); -- Verify relevance.
-- processor
gpio15_pinr : aliased io_reg_t;
for gpio15_pinr'address use temp0'address;
gpio15_ddr : aliased io_reg_t;
for gpio15_ddr'address use temp1'address;
gpio15_portr : aliased io_reg_t;
for gpio15_portr'address use temp2'address;
gpio15_pin : constant interfaces.unsigned_8 := 1;
procedure core_reg_write_old (reg: in out unsigned_8; value: in unsigned_8) is
begin
reg := value;
end core_reg_write_old;
procedure core_reg_write (reg: access io_reg_t; value: in unsigned_8) is
begin
reg.io_reg := value;
end core_reg_write;
function core_reg_read (reg: access io_reg_t) return Unsigned_8 is
begin
return reg.io_reg;
end core_reg_read;
-- gpio
type gpio_t is record
pinr : access io_reg_t;
ddr : access io_reg_t;
portr : access io_reg_t;
pin : interfaces.unsigned_8;
end record;
pragma volatile(gpio_t); -- Verify relevance.
procedure gpio_output (gpio : in gpio_t) is
begin
core_reg_write(gpio.ddr,core_reg_read(gpio.ddr) or shift_left(1,integer(gpio.pin)));
end gpio_output;
procedure gpio_hi (gpio : in gpio_t) is
begin
core_reg_write(gpio.portr,core_reg_read(gpio.portr) or shift_left(1,integer(gpio.pin)));
end gpio_hi;
procedure gpio_lo (gpio : in gpio_t) is
begin
core_reg_write(gpio.portr,core_reg_read(gpio.portr) and not shift_left(1,integer(gpio.pin)));
end gpio_lo;
gpio15 : gpio_t := (
pinr => gpio15_pinr'access,
ddr => gpio15_ddr'access,
portr => gpio15_portr'access,
pin => gpio15_pin
);
-- led
type led_t is record
gpio : gpio_t;
end record;
led0 : led_t := (gpio => gpio15);
procedure led_init (led : in led_t) is
begin
gpio_output(led.gpio);
end led_init;
procedure led_on (led : in led_t) is
begin
gpio_hi(led.gpio);
end led_on;
procedure led_off (led : in led_t) is
begin
gpio_lo(led.gpio);
end led_off;
begin
put_line("Hello, world!");
-- Does it match the original value of 5?
put_line(gpio15.pinr.io_reg'Image);
-- Does modification via variable alter the value returned?
temp0 := 203;
put_line(gpio15.pinr.io_reg'Image);
-- Does modification via record alter the value returned?
gpio15.pinr.io_reg := 89;
put_line(gpio15.pinr.io_reg'Image);
-- Writes value in temp2 (128) to temp0.
core_reg_write_old(temp0,temp2);
put_line(gpio15.pinr.io_reg'Image);
put_line(gpio15.ddr.io_reg'Image);
put_line(gpio15.portr.io_reg'Image);
put_line(gpio15.pin'Image);
-- Writes value of pin (1) to pinr via record.
--core_reg_write(gpio15.ddr,gpio15.pin);
-- Writes 1 shifted value of pin times and or's that with ddr reg
--gpio_output(gpio15);
led_init(led0);
put_line(gpio15.pinr.io_reg'Image);
put_line(gpio15.ddr.io_reg'Image);
put_line(gpio15.portr.io_reg'Image);
put_line(gpio15.pin'Image);
--gpio_hi(led0.gpio);
led_on(led0);
put_line(gpio15.pinr.io_reg'Image);
put_line(gpio15.ddr.io_reg'Image);
put_line(gpio15.portr.io_reg'Image);
put_line(gpio15.pin'Image);
--gpio_lo(led0.gpio);
led_off(led0);
put_line(gpio15.pinr.io_reg'Image);
put_line(gpio15.ddr.io_reg'Image);
put_line(gpio15.portr.io_reg'Image);
put_line(gpio15.pin'Image);
end Hello;
I modified this for my embedded environment but it failed to compile, with the complaint:
undefined reference to `__gnat_last_chance_handler’
for the lines “reg.io_reg := value” and “return reg.io_reg”.
I found out that I actually didn’t need the last_chance_handler if my access types were explicitly declared to be “not null”.
So the updated program became:
with Interfaces; use Interfaces;
with System;
with Atmega328p;
procedure Main is
-- core
type io_reg_t is limited record
io_reg : interfaces.unsigned_8;
end record;
pragma volatile(io_reg_t); -- Verify relevance.
type dd_io_reg_t is new io_reg_t;
-- Location?
gpio15_pinr : aliased io_reg_t;
for gpio15_pinr'address use Atmega328p.PINB'Address;
gpio15_ddr : aliased io_reg_t;
for gpio15_ddr'address use Atmega328p.DDRB'Address;
gpio15_portr : aliased io_reg_t;
for gpio15_portr'address use Atmega328p.PORTB'Address;
gpio15_pin : constant interfaces.unsigned_8 := 1;
procedure core_reg_write (reg: not null access io_reg_t; value: in interfaces.unsigned_8) is
begin
reg.io_reg := value;
end core_reg_write;
function core_reg_read (reg: not null access io_reg_t) return interfaces.unsigned_8 is
begin
return reg.io_reg;
end core_reg_read;
-- gpio
type gpio_t is record
pinr : not null access io_reg_t;
ddr : not null access io_reg_t;
portr : not null access io_reg_t;
pin : interfaces.unsigned_8;
end record;
pragma volatile(gpio_t); -- Verify relevance.
-- gpio_output
procedure gpio_output (gpio : in gpio_t) is
begin
core_reg_write(gpio.ddr,core_reg_read(gpio.ddr) or shift_left(1,integer(gpio.pin)));
end gpio_output;
procedure gpio_hi (gpio : in gpio_t) is
begin
core_reg_write(gpio.portr,core_reg_read(gpio.portr) or shift_left(1,integer(gpio.pin)));
end gpio_hi;
procedure gpio_lo (gpio : in gpio_t) is
begin
core_reg_write(gpio.portr,core_reg_read(gpio.portr) and not shift_left(1,integer(gpio.pin)));
end gpio_lo;
gpio15 : gpio_t := (
pinr => gpio15_pinr'access,
ddr => gpio15_ddr'access,
portr => gpio15_portr'access,
pin => gpio15_pin
);
-- led
type led_t is record
gpio : gpio_t;
end record;
led0 : led_t := (gpio => gpio15);
procedure led_init (led : in led_t) is
begin
gpio_output(led.gpio);
end led_init;
procedure led_on (led : in led_t) is
begin
gpio_hi(led.gpio);
end led_on;
procedure led_off (led : in led_t) is
begin
gpio_lo(led.gpio);
end led_off;
begin
-- Initialize
-- Writes value of pin (1) to pinr via record.
--core_reg_write(gpio15.ddr,gpio15.pin);
-- Writes 1 shifted value of pin times and or's that with ddr reg
--gpio_output(gpio15);
led_init(led0);
For_loop_0:
loop
-- turn on
--gpio_hi(led0.gpio);
led_on(led0);
-- loop
Lazy_delay_1:
for i in interfaces.unsigned_32 range 0 .. 100_000 loop
null;
end loop Lazy_delay_1;
-- turn off
--gpio_lo(led0.gpio);
led_off(led0);
-- loop
Lazy_delay_2:
for i in interfaces.unsigned_32 range 0 .. 100_000 loop
null;
end loop Lazy_delay_2;
end loop For_loop_0;
end Main;
After this modification I compiled it burned it into the microcontroller.
And the led blinks.
I'll use this moving forward.
I use AXI IIC BUS IP Core on vivado.Even if I write the corresponding data to the register, there is no change on the sda data line.
Here is the registers of the ip core.
Register of AXI IIC
And the Programming Sequence are as below.
The sequence
It seems that I only need to complete the first and the second steps and I can see the wavefrom changes on SDA
Here is a top.v (it's not my original,reference from Bad s_axi_bvalid, s_axi_wready, and s_axi_awready signals using Vivado IIC IP Flow)
module i2c_channel #(
parameter CHANNEL_OUTPUT_WIDTH = 16
)(
input clk,
input reset,
//the address of the slave;
input [6:0] slave_address,
//The width of the message expected from the slave at the specified address;
input [127:0] slave_message_width,
inout sda,
inout scl,
output [CHANNEL_OUTPUT_WIDTH - 1:0] channel_output
);
wire iic2intc_irpt ;
//AXI Global System Signals
wire s_axi_aclk;
assign s_axi_aclk = clk;
reg s_axi_aresetn ;
//AXI Write Address Channel Signals
reg [31:0] s_axi_awaddr ;
reg s_axi_awvalid ;
wire s_axi_awready;
//AXI Write Data Channel Signals
reg [31:0] s_axi_wdata ;
reg [3:0] s_axi_wstrb ;
reg s_axi_wvalid ;
wire s_axi_wready;
//AXI Write Response Channel Signals
wire [1:0] s_axi_bresp;
wire s_axi_bvalid;
reg s_axi_bready ;
//AXI Read Address Channel Signals
reg [31:0] s_axi_araddr ;
reg s_axi_arvalid ;
wire s_axi_arready;
//AXI Read Data Channel Signals
wire [31:0] s_axi_rdata;
wire [1:0] s_axi_rresp;
wire s_axi_rvalid;
reg s_axi_rready ;
//IIC signals
reg sda_i ;
wire sda_o;
wire sda_t;
reg scl_i ;
wire scl_o;
wire scl_t;
reg gpo ;
reg state_done;
//i2C state
`define SET_TX_FIFO 4'b0000
`define SET_RX_FIFO_PIRQ 4'b0001
`define SET_CR_MSMS_TX 4'b0010
`define SET_CR_TXAK 4'b0011
reg[3:0]state;
initial begin
state<=4'b0000;
end
//tri-state open-collector buffers to convert the iic signals to bi-directional inouts.
assign sda = sda_t ? sda_o : sda_i;
assign sda = scl_t ? scl_o : scl_i;
axi_iic_1 iic (
.iic2intc_irpt(),
.s_axi_aclk(s_axi_aclk),
.s_axi_aresetn(s_axi_aresetn),
.s_axi_awaddr(s_axi_awaddr[8:0]),
.s_axi_awvalid(s_axi_awvalid),
.s_axi_awready(s_axi_awready),
.s_axi_wdata(s_axi_wdata),
.s_axi_wstrb(s_axi_wstrb),
.s_axi_wvalid(s_axi_wvalid),
.s_axi_wready(s_axi_wready),
.s_axi_bresp(s_axi_bresp),
.s_axi_bready(s_axi_bready),
.s_axi_bvalid(s_axi_bvalid),
.s_axi_araddr(s_axi_araddr),
.s_axi_arvalid(s_axi_arvalid),
.s_axi_arready(s_axi_arready),
.s_axi_rdata(s_axi_rdata),
.s_axi_rvalid(s_axi_rvalid),
.s_axi_rresp(s_axi_rresp),
.sda_i(sda_i),
.sda_o(sda_o),
.sda_t(sda_t),
.scl_i(scl_i),
.scl_o(scl_o),
.scl_t(scl_t)
);
always #(clk) begin
s_axi_aresetn <= ~reset;
end
//Sets an axi data write operation.
task set_a_axi_w ;
input [31:0] awaddr;
input [31:0] wdata;
begin
s_axi_awaddr = awaddr;
s_axi_wdata = wdata;
s_axi_awvalid = 1'b1;
s_axi_bready = 1'b1;
s_axi_wvalid = 1'b1;
end
endtask
//set the state of the operation.
task set_state ;
input[3:0] new_state;
if(s_axi_awready) begin
state = new_state;
end
endtask
//when the module is initialized, write the i2c address of the target slave to the TX_FIFO register.
//Write the IIC peripheral device addresses for the first slave device to the TX_FIFO.
always #(posedge clk) begin
case (state)
`SET_TX_FIFO : begin
set_a_axi_w(32'h108, slave_address);
set_state(`SET_RX_FIFO_PIRQ);
end
`SET_RX_FIFO_PIRQ : begin
set_a_axi_w(32'h120, slave_message_width - 2);
set_state(`SET_CR_MSMS_TX);
end
`SET_CR_MSMS_TX : begin
set_a_axi_w(32'h100, 8'b00000101);
set_state(`SET_CR_TXAK);
end
endcase
if(s_axi_awready) begin
//s_axi_awaddr <= '0;
s_axi_awvalid <= 1'b0;
end
if(s_axi_wready) begin
//s_axi_wdata <= '0;
s_axi_wvalid <= 1'b0;
end
if (s_axi_bvalid) begin
s_axi_bready <= 1'b0;
end/**
else begin
s_axi_bready <=1'b0;
end**/
end
endmodule
Here is the simulation file:
module i2c_channel_tb();
//Parameters
parameter CLK_PERIOD = 10;
reg clk = 1'b1;
reg reset = 1'b1;
reg [6:0] slave_address = 6'b0;
wire sda;
wire scl;
i2c_channel i2c_channel_1 (
.clk(clk),
.reset(reset),
.slave_address(slave_address),
.slave_message_width(128'd16),
.sda(sda),
.scl(scl)
);
/*
i2c_channel_slave_model i2c_channel_slave_model_1 (
.sda(sda),
.scl(scl)
);**/
initial begin
clk <= 1'b0;
reset <= 1'b0;
slave_address <= 7'b100_1011;
end
//psuedo-clock
always #10 begin
clk <= ~clk;
end
endmodule
the waveform result is below:
Result waveform
As shown in the figure,the data is written to the register,but no changes occur on the sda_o and scl_o.
Could anybody tell me why?
Thanks!
One obvious mistake is that your SDA and SCL should be pulled up in their initial state.
Your waveform shows that sda_o, sda_i, scl_o, and scl_i are not pulled up at any given time. So perhaps you need to check that. (Either it is some hardware schematic to check, or some IO constraint to set.)
I'm using active-hdl to simulate my FPGA designs and I'd like to know if it's possible to use dynamically generated strings to represent my signals in the simulator. For example, let's say I have a 4-bit std_logic_vector containing an op-code, I'd like the simulator to display the op-code strings "nop", "add", "sub" etc instead of the vector value.
I tried declaring a custom enumeration type at first but quickly discovered that you don't get to choose the values of the individual elements. My next solution was to use the enumeration for simulation display only and convert with a translation function:
type op_code_type is (nop, add, sub, unknown); -- not in order
signal op_code_str: op_code_type;
signal op_code: std_logic_vector(3 downto 0);
function to_string(op_code : std_logic_vector(3 downto 0))
return op_code_type is
begin
case op_code is
when "0000" => return nop;
when "0010" => return add;
when "0011" => return sub;
when others => return unknown;
end case;
end to_string;
begin
----- testbench -----
process
begin
op_code <= "0000";
wait for 1ns;
op_code <= "0001";
wait for 1ns;
op_code <= "0010";
wait for 1ns;
op_code <= "0011";
wait for 1ns;
end process;
op_code_str <= to_string(op_code);
end architecture;
This actually works quite well, and is probably adequate for most things I want to do:
The main problem though is I'm stuck with string constants, so it'll be too impractical for more complex stuff like mov acc,x and all the many other variants a real-world design would have.
Are there ways to construct dynamic simulation identifiers like this? Or is it a fundamental limitation of HDLs?
In Modelsim, you can use virtual types and functions. For example, consider the following vector:
signal opcode : std_logic_vector(2 downto 0);
You can then at the Modelsim command line define a virtual type, such as:
virtual type {{0 nop} {1 load} {2 store} {3 invalid}} opcode_type
This is a type known only to the simulator. You can then create a virtual signal based on this type to convert the vector, such as:
virtual function {(opcode_type)opcode} opcode_str
Then wave opcode_str, and it will give you a custom formatted string..
I don't know if you can do the same with Active-HDL.
Now, as for doing it dynamically, the only possibility might be if the returned string is defined by a TCL function, such as:
# TCL code to read a file, or otherwise dynamically generate opcodes
# returning the appropriately formatted virtual type
proc generate_opcode_type {} {
...
}
virtual type [generate_opcode_type] opcode_type
virtual function {(opcode_type)opcode} opcode_str
Then wave opcode_str.
For posterity, and at the request of #B. Go, here is my previous answer:
#Paebbels has it. We use this frequently, especially when doing post place-and-route simulations to convert state codes to their equivalent enumerated type. So for completeness, I'll show you how we do it. The example below considers a case where binary encoding is used. If trying to convert from grey or one-hot, things are a bit different. For one-hot, I tend to use a function.
Consider an 3-bit vector with associated names:
|-----------|----------|
| 000 | Idle |
| 001 | Start |
| 010 | Running |
| 011 | Paused |
| 100 | Done |
| 101 - 111 | Invalid |
|-----------|----------|
So, if you have a signal, such as:
signal opcode : std_logic_vector(2 downto 0);
Then you want to convert to an enumerated type, which will show up cleanly in your waveform viewer. First, create the enumerated type and associated signal:
type opcode_names is (idle, start, running, paused, done, invalid);
signal opcode_name : opcode_names;
Then it is a simple with/select:
with to_integer(unsigned(opcode)) select
opcode_name <= idle when 0,
start when 1,
running when 2,
paused when 3,
done when 4,
invalid when others;
Though if you have a complete set, it is a bit simpler. Consider a 2-bit vector with names "idle, start, running, done".
type opcode_names is (idle, start, running, done);
signal opcode_name : opcode_names;
...
opcode_name <= opcode_names'image(to_integer(unsigned(opcode));
For more complex vectors with unusual, non-contiguous values, I typically use a function, such as:
signal opcode : std_logic_vector(31 downto 0);
type opcode_names is (idle, start, running1, running2, paused, done, invalid);
signal opcode_name : opcode_names;
function get_opcode_name(opcode : in std_logic_vector) return opcode_names is
variable ret : opcode_names;
begin
case to_integer(unsigned(opcode)) is
when 0 =>
ret := idle;
when 13 =>
ret := start;
when 87 =>
ret := running1;
when 131 =>
ret := running2;
when 761 =>
ret := paused;
when 3213 =>
ret := done;
when others =>
ret := invalid;
end case;
return ret;
end function get_opcode_name;
...
opcode_name <= get_opcode_name(opcode);
Is it possible to use VHDL style entity instantiation to include a Verilog module in a VHDL design?
I realize that I can accomplish this if I treat the Verilog module as a component and instantiate the component.
Thanks
Grab from my collection of code:
module sync_fifo
#(parameter WIDTH = 8, // width in bits
L2DEPTH = 4, // Log 2 Depth, 4=16 deep
REGFLAGS = 1 // Full, empty are registered
)
(
input clk, // system clock
input reset_n, // A-synchronous low reset/clear
input enable, // clock gating
input clear, // Synchronous clear
input write, // write FIFO
input [WIDTH-1:0] wdata, // write data
input read, // read FIFO
output [WIDTH-1:0] rdata, // read data
output reg empty, // FIFO is empty
output reg full, // FIFO is full
output reg [L2DEPTH:0] level // Fill level
);
outp_fifo : sync_fifo
generic map(
WIDTH => 10, -- Byte + user + last
L2DEPTH => 7, -- 128 deep
REGFLAGS=> 1
)
port map
(
clk => ACLK, -- system clock
reset_n => ARESETN, -- A-synchronous low reset/clear
enable => BIT_1 , -- clock gating
clear => BIT_0 , -- Synchronous clear
write => package_byte_en, -- write FIFO
wdata => outp_fifo_wt_data_and_meta , -- write data
read => outp_fifo_read , -- read FIFO
rdata => outp_fifo_rd_data_and_meta , -- read data
empty => outp_fifo_empty, -- FIFO is empty
full => outp_fifo_full, -- FIFO is full
level => open -- Fill level
);
Post edit:
And you can only do that with a competent declaration:
COMPONENT sync_fifo IS
generic(
WIDTH : integer := 8;
L2DEPTH : integer := 8;
REGFLAGS : integer := 1
);
PORT (
clk : in STD_LOGIC; -- system clock
reset_n: in STD_LOGIC; -- A-synchronous low reset/clear
enable : in STD_LOGIC; -- clock gating
clear : in STD_LOGIC; -- Synchronous clear
write : in STD_LOGIC; -- write FIFO
wdata : in STD_LOGIC_VECTOR(WIDTH-1 downto 0); -- write data
read : in STD_LOGIC; -- read FIFO
rdata : out STD_LOGIC_VECTOR(WIDTH-1 downto 0); -- read data
empty : out STD_LOGIC; -- FIFO is empty
full : out STD_LOGIC; -- FIFO is full
level : out STD_LOGIC_VECTOR(L2DEPTH downto 0) -- Fill level
);
END COMPONENT;
Although the answer seemed to be "no" 3 years ago when this was asked, I have found the answer in practice to be "yes you can", but with some caveats. I confirmed this using Mentor ModelSim and Xilinx Vivado.
I don't know if something about the LRM changed or if the tool vendors just decided to support it anyway. I'm using VHDL-2008, so I suspect the latter.
Here's an instance I have in one of my test files. VerFlopX is a Verilog module.
VerFlopX8c: entity work.VerFlopX (rtl)
generic map (SIZE => 8) --integer:=1
port map (
Clk => Clk, --in wire
D => D16(15 downto 8) , --in wire[(SIZE-1):0]
Q => Q16(15 downto 8)); --out wire[(SIZE-1):0]
The caveats:
Note that architecture "rtl" is specified. This can (and probably should) be omitted. But it still works, which seems weird since Verilog modules don't have architectures.
You cannot associate the Q output with open. ModelSim will return an error. If you change to a component instantiation, having a formal output connected to open is fine.