UVM: unified verification methodology

UVM was created in 2009. It's just a bunch of sv files with lot of tasks, functions, classes, etc already written, so that you don't have to write it. You just have to learn how to call them.

NOTE: In sv, classes are created at runtime, as opposed to verilog/vhdl, where all instances are elaborated before runtime.

Go thru SV section, and then come back here to go thru this detailed SV verification document written very nicely. It explains basics of verification (Not UVM necessarily).

Link => Springer_-_SystemVerilog_for_Verification.pdf

Running uvm in irun:


We can use irun/xrun with -uvm option which then allows us to access cadence uvm library. Top level design units that show while compiling are uvm_pkgs (uvm_pkg.sv cdns_uvm_pkg.sv cdns_uvmapi cdns_assert2uvm_pkg) and tb.sv

uvm library files are usually installed in these paths for Cadence simulator:

ies: /apps/cds/incisiv/14.20.01/tools/methodology/UVM/CDNS-1.1d/additions/sv/ => for uvm 1.1 (all files are system verilog files)

xcelium: /home/.../cadence/xcelium/19.01.v001/tools/methodology/UVM/CDNS-1.2/sv/ => for uvm 2.0

Within this "sv" dir are multiple dir. Source code is in subdir named "src"

src/uvm_pkg.sv => It's a package, with bunch of include files (*.svh) in it

src/uvm_macros.svh => It has bunch of include files for macro defn (*_defines.svh) in it. This is not a package, just a regular file with include files which have various macro defines (i.e `define uvm_delay(TIME) #(TIME);)

There are bunch of more dir in "src" that has all of these include files, and all other files needed by uvm (i.e src/base/uvm_root.svh has run_test task, ..).

Below is the simplest UVM testbench written to test uvm functionality

sample tb testbench: => no test being called in this. Just prints "Hello"

module tb;

import uvm_pkg::*; // uvm package imported, these 2 lines needed
`include "uvm_macros.svh" //uvm macro included (to get all defn)

initial begin
  `uvm_info("TEST", "Hello!!!", UVM_LOW) //NOTE: no semicolon at end since it's replaced by the macro defn found in uvm_macros.svh
end

endmodule : tb

cmd: irun -uvm tb.sv => -uvm compiles uvm pkgs (uvm_pkg.sv cdns_uvm_pkg.sv). Then module tb is compiled. Then ncsim is run, which causes "initial" block in tb to run. It prints "HELLO!!!" from that block

screen o/p:
-----------
SVSEED default: 1                                                               
ncsim> source /apps/cds/incisiv/14.20.017p1/tools/inca/files/ncsimrc                      
ncsim> source /apps/cds/incisiv/14.20.017p1/tools/methodology/UVM/CDNS-1.1d/additions/sv/files/tcl/uvm_sim.tcl    => This has bunch of tcl proc which parse various uvm cmds for option correctness, before calling the uvm package (i.e uvm_version proc finally calls uvm_pkg::uvm_version)
UVM_INFO tb.sv(12) @ 0: reporter [TEST] Hello!!!
ncsim: *W,RNQUIE: Simulation is complete.       
ncsim> exit    

----------------------------------------------
Now we modify tb above to include a test to run  
----------------------------------------------
sample tb testbench: =>  test being called in this

module tb;

// uvm package/macro, these 2 lines below needed
import uvm_pkg::*;
`include "uvm_macros.svh"

 wire a, b,..; //nets, reg etc defined which are used here

 digtop u_digtop (.A(wire1), ...); //inst digtop
 pullup(GPIO1); //inst other modules
 i2c_bfm u_i2c_bfm (.SCL(SCL), ...);

 always @(a) begin .. end //for capturing events

 //simple interface for interfacing internal nets => we always connect dut signals to interface, and then interface interacts with our uvc. This is to make it resuable. We use virtual interface (which then needs to be connected to real interface). config database is used to set these virtual i/f.
 gpio_interface gpio_if (.pclk(`TB.u_dig_top.clk_16m),.data(..),...);
 adc_interface adc_if (....);

//ex of interface: (see pg 300 of UVM lecture notes from cadence)
interface (input clk, rst);
 logic [7:0] in_data;
 logic ...;
endinterface

 //configuring interface ..
initial begin
 //components can be configured using uvm_config_db.  uvm_config_db is a class that has set and get function defined in it. We can supply the name of field, and value to be set or get using this uvm_config_db
 uvm_config_db #(virtual gpio_interface)::set(null, "*", "gpio_vif", gpio_if); //gpio_vif is set to value gpio_if
 uvm_config_db #(virtual adc_interface)::set(null, "*", "adc_vif", adc_vif);
end

initial begin
  `uvm_info("TEST", "Hello!!!", UVM_LOW)
   run_test(); // calling test to run. run_test is a task in .../CDNS-1.1d/sv/src/base/uvm_root.svh  [task uvm_root::run_test(string test_name=""); ... endtask] which calls run_phases [fork ... uvm_phase::m_run_phases(); join_none] and finally does $finish, once all done. All uvm tasks are in base dir. i.e base/uvm_phase.svh, etc. So, no explicit $finish needed in our testcase.
end

initial begin
  #10ms;
  $finish; //for sim timeout (there's already a $finish in uvm run_test call)
end

//code below differs for separate testcases. So, we can keep below code in separate files  so that each test has it's own tc.sv file. similar to how we do our regular testcases in non-uvm env. In this ex, testcase is part of same file as tb.

/////////////// testcase start = base_test.sv /////////////
NOTE: classes are same as modules. They have local signals, function, etc
`define testname base_test

class `testname extends uvm_test; //uvm_test is built in uvm class for tests.
  `uvm_component_utils(`testname)

  byte mask[]; //dynamic byte array of undefined length

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction : new

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);        
    `uvm_info("TEST", "Running base Test", UVM_LOW)
  endfunction : build_phase
   
  //more func and task (everything below optional and not included for irun)
  virtual function void end_of_elaboration_phase(uvm_phase phase);
     super.end_of_elaboration_phase(phase);        
    `uvm_info("TEST", "End of elab: Sample Test", UVM_LOW)
  endfunction : end_of_elaboration_phase
   
  function init_test_seq(test_seq_base test_seq);
     test_seq.env = this.env; ...
  endfunction

  //tasks
  task run_phase(uvm_phase phase); //NOTE: we can also use task "main_phase" instead of "run_phase"
    phase.raise_objection(this, "base_test"); //raise obj, uvm keeps track of how many obj raised
    ... // all tests done here
    phase.drop_objection(this, `teststr);      // drop obj, only when all obj dropped, $finish called by uvm
  endtask:

   //more tasks
   task i2c_read(input bit [6:0] addr, input byte reg output byte data[]);
    ...
   endtask

endclass : base_test
/////////////////////// testcase end /////////////
   
endmodule : tb

cmd:
---
irun -uvm tb.sv => calls default test (base_test) to run.
irun -uvm tb.sv +UVM_TESTNAME=base_test => or we can provide test name explicitly if there are many tests. same as above if only 1 test.
If base_test is in separate file (other than tb.sv), then we need to give name of that file too so that irun can compile/elaborate it, i.e
irun -uvm tb.sv base_test.sv +UVM_TESTNAME=base_test

screen o/p:
----------
UVM_INFO tb.sv(12) @ 0: reporter [TEST] Hello!!!
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO tb.sv(29) @ 0: uvm_test_top [TEST] Running Base Test  => prints "Running Base test" from function above      

--- UVM Report catcher Summary ---
Number of demoted UVM_FATAL reports  :    0
Number of demoted UVM_ERROR reports  :    0
Number of demoted UVM_WARNING reports:    0
Number of caught UVM_FATAL reports   :    0
Number of caught UVM_ERROR reports   :    0
Number of caught UVM_WARNING reports :    0

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO :    3             
UVM_WARNING :    0          
UVM_ERROR :    0            
UVM_FATAL :    0            
** Report counts by id      
[RNTST]     1               
[TEST]     2                
Simulation complete via $finish(1) at time 0 FS + 179 => NOTE: here sim finishes via $finish
/apps/cds/incisiv/14.20.017p1/tools/methodology/UVM/CDNS-1.1d/sv/src/base/uvm_root.svh:457     $finish;
ncsim> exit                           

-----------------------------------
we can create another testcase called sample_test which can extend base_test (in sample_test.sv)
//////////////////////// sample_test.sv => testcase start /////////////

`define testname sample_test
class `testname extends base_test;
  `uvm_component_utils(`testname)

  byte mask[]; //dynamic byte array of undefined length

 //these function defn below may not be needed
 extern function new(string name = `teststr, uvm_component parent = null); //defined as external function, so compiler looks for these function outside the class
 extern function void build_phase(uvm_phase phase);
 extern task run_phase(uvm_phase phase);
 extern task main_phase(uvm_phase phase);

endclass

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction : new

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);        
    `uvm_info("TEST", "Running sample Test", UVM_LOW)
  endfunction : build_phase
   
  //more functions
  function check_val (byte data1, int i);
     ....
  endfunction

  //main task
  task `testname::main_phase(uvm_phase phase);
   int a,b;
   super.main_phase(phase); //super fn always called for phases before doing anything else
   phase.raise_objection(this, "sample_test"); //raise obj
     $display(`testname:: main_phase");
     #10; addr = ... //write like normal task
     #1ms;
   phase.drop_objection(this, `teststr);      // drop obj
  endtask: main_phase
/////////////////////// testcase end /////////////

-------------------------------
see UVM lect from Cadence 5 day workshop:

tb consists of "reusable verification component (UVC)" + DUT. It also has sv i/f to link VC to DUT.
testcases can be put in separate files or within tb.

UVC consists of
 - sequencer = to create high level data (as do_read), either random or under control of test data (has TX transmitter)
 - driver (BFM=Bus functional Model) = to drive i/f signals to DUT (from high level to low level signals)
 - Monitor = to capture activity on i/f for coverage, checking and analysis (has RX receiver)

Top level of UVC is "Env". Env contains one or more agents. Each agent contains inst of Sequencer, driver, monitor and config. config indicates if it'a active agent (i.e TX => all seq, driver, monitor implemented) or passive agent (i.e RX => only monitor implemented)
All above components are created using class.
class sequencer extends base ... endclass
class driver extends base ... endclass
class monitor extends base ... task monitor(bit[3:0] ..) forever begin ... end endtask endclass
interface pds_if(input ...) .. endinterface

//PDS UVC => top level uvc which inst all of the above components
class pds_vc extends base; ... endclass

On top of this, scorboard UVC is also added to tb, so that i/p and o/p to/from DUT can be compared and errors flagged.  
UVM phases: (pg 177 of UVM doc from cadence) These are predefined phases. User defined phases can also be added. Phases start running in the order below when global task run_test() is called.
NOTE: to use phases below, we simply declare *_phase function in your class, and add reqd functionality. We should not call *_phase directly. It's automatically executed by simulator during that phase.
 - build_phase => building testbench. All hier should be built using build_phase rather than constructors. build_phase executes top down, all other phases execute bottom up.
 - connect_phase => making connections. Any connections b/w components should be made using connect_phase rather than constructors
 - end_of_elaboration_phase => for any pre run activity
 - run_phase => simulation of design. run_phase is implemented as a task, as it can consume time. Rest all phases are implemented as functions, as they run in 0 time. run_phase has many subphases and were added in UVM1.0. These sub-phases run in parallel with run_phase.
 - check_phase, report_phase => checking results and generating reports
 
----------------------------
Built in classes (chapter 4 in uvm book)
---------
UVM lib has many inbuilt classes from which we extend to create our classes. Ex:
1. uvm_object: this is the base class for all (data and component) UVM classes. It has built in methods for copy(), compare(), clone(), print(). Most of the time, we derive our data item from uvm_sequence_tem which itself extends from uvm_transaction which extends from uvm_object.
 ex:
class apb_transfer extends uvm_object; //or "extends uvm_sequence_item"  can also be used
      ...
  `uvm_objects_utils_begin(apb_transfer) //uvm automation macros => used to declare common operaations
     `uvm_field_int(addr, UVM_DEFAULT)
  `uvm_objects_utils_end
  function new (string name = "apb xfer"); //uvm construction new() - not mandatory
   super.new(name);
  endfunction
endclass

2. uvm_component: All infrastructure components as testbench comp, UVCs, tests are derived directly or indirectly from uvm_component. Typically we derive our user classes from methodology classes (uvm_agent, uvm_driver, uvm_sequencer, uvm_monitor, uvm_scoreboard, uvm_test and so on), which are themselves extension of uvm_component. Phases and configuration methods are functionality provided by uvm_component base class.
ex:
class testbench_comp extends uvm_component; //testcase
  function new(string name, uvm_component parent)
   super.new(name,parent);
  endfunction
  function void build_phase(uvm_phase phase);
   super.build_phase(phase);
   my_uvc = if_comp::type_id::create("my_uvc", this);
  endfunction
  //similarly other function for end_of_elaboration_phase, etc can be added here
  task run_phase (uvm_phase phase); //this is where the testcase is coded and run
   ...
  endtask
endclass

class if_comp extends uvm_component; //this is some other class that has many uvm_phases
 function new ... endfunction
 function void build_phase ... endfunction
 task run_phase ... endtask
endclass

module test; //testbench to run above testcase
 testbench_comp tb;
 initial begin
  tb = testbench_comp::type_id::create("testbench", null);
  run_test(); //this call starts the uvm phases for all the classes above
 end
endmodule

-----------------
contraints:
-------
In any class, we can have constraints defined for any field.
ex: constraint c_addr {addr[1:0] == 2'b01; }

we can overwrite any constraint in subclass, by redeclaring. Parent class contraint can be removed in subclass by leaving it empty.
ex: constarint c_addr {} => constraint c_addr is removed for this subclass, even though it's present in parent class.

-----------
coverage in uvm: see system verilog notes also:
----------

Metric driven verification
1. functional coverage
2. assertions (static formal verification and sims)
3. directed tests
4. code coverage

Most coverage constructs are placed in monitors.

1. functional coverage of 2 types:
A. assertions for control oriented coverage => cannot be in class
ex:
property req_gnt (cyc);
 @(posedge clk) req |=> ##[cyc] gnt;
endproperty

cover property (req_gnt(3));
cover property (req_gnt(4));

B. covergroups for data-oriented coverage => can be declared as class member
ex:
covergroup cg @(posedge clk);
 len: coverpoint pkt.length { ...}
 addr: cross pkt.addr, len;
endgroup

cg cg1 = new();

--------------

ARM: It was initially called Advanced Risc Machines Ltd, but later changed the name to ARM Ltd. It licenses CPU cores (cortex lineup), as well as GPU cores (mali lineup). NOTE: ARM primarily licenses processor cores, and NOT microprocessor or microcontroller. However, they also provide licences at system level and software level for companies which want to get the full pkg. It's cores can be used anywhere, although they are mostly used in microcontrollers. It licenses these designs (core license), as well as it allows companies to license it's instruction set (architectural license), so that they can build their own design any way they like. Apple and Qualcomm use architectural license from ARM to design their mobile processors. For Core licenses, companies receive ARM synthesizable core IP in verilog (i.e processor RTL). These IP are called soft IP, as they are in RTL. Synthesis and PnR are carried out by the companies getting these licenses.

ARM was formed in 1990, and introduced ARM6 processor family in 1991. It was based on ARM v4T processor architecture. Then came ARM7, which were still based on v4T, most popular of which was ARM7TDMI. ARMv5E arch was introduced with ARM9E processor families (E stands for enhanced, which added DSP inst for multimedia processing). Then came the ARM11 processor family based on v6 arch. At this time, it was decided to branch processor families into different types, based on their use profile. Also, each processor family would be based on arch most suited for that application type. This resulted in new product portfolio from ARM called cortex family of processors.These were subdivided into 3 profiles based on application usage. The 3 profiles were A, R, M (discussed later). New v7 arch was introduced for the cortex family. Each of these profiles had their own tailored arch, namely v7-A, v7-R and v7-M.

Though ARM had it's classic cores starting from 1990's, it's more popular "cortex" cores started appearing from 2004. These are the cores that you see everywhere in designs, the classic cores (ARM6, ARM7, etc) were used in older devices, and have disappeared from mass market. One reason for success of ARM cortex lineup is that they became very cheap, with some cortex M0 based microcontrollers sold for as low as 25 cents. This allowed these 32 bit cores to replace 8 and 16 bit microcontrollers. Let's talk about ARM ISA before delving into profiles and arch.

 

VNC: Virtual Network Computing

VNC is a graphical desktop sharing system, used to remotely control another PC. It's same as other software as chrome remote desktop,  TeamViewer, , etc which allow you to control remote PC. VNC is very popular among enterprises, and is open source under GNU license. VNC was orinally developed in UK. Many other commercial or open source products based on VNC original source code developed. In 2002, VNC R&D center was closed. It's developers formed RealVNC which developed open source as well as commercial product under the same name. Most of the time when people say VNC, they mean RealVNC.

Intro material on Wiki: https://en.wikipedia.org/wiki/Virtual_Network_Computing

 

VNC server/client model:

VNC software has 2 parts: a sever software, and a client software. You install server software on the desktop which you want to control. You install client software on the desktop from where you want to control. The client software knows how to connect to server software. The client software displays the desktop screen on remote desktop which is running the server software for VNC.

Installation:

Install RealVNC from here: You will need to install both RealVNC server and RealVNC client. They will need to be installed on different computers. Server on the computer to be controlled, and client on the computer that controls the server computer. Choose appropriate OS and then download it.

Real VNC server: https://www.realvnc.com/en/connect/download/vnc/

RealVNC client: https://www.realvnc.com/en/connect/download/viewer/

Running VNC:

Once installed, you can start VNC server on server desktop by clicking on RealVNC icon or typing "vncserver" on terminal. Once started, VNC server always starts on powerup. OWhen vncserver is running, it shows the ip address for the computer on which it is running. It's something like this kind of message:

$vncserver

......

New desktop is raspberrypi (192.168.1.109)

....

Now on the client machine (where you have the VNC client software installed), you enter this number in address section (here, it's 192.168.1.109). Once enetered, it brings up an icon, on which you click, and you can see the remote desktop screen (where VNC server is running). Now if you work on this screen, it seems as if you are directly working on the remote desktop (the screen refreshes amazingly fast. Keystrokes from client to server, as well as pixels from server to client are transmitted pretty fast, especially if both client and server are connected to high speed internet).

vncserver command on Linux terminal can be used with a lot of options to set the display options. One helpful option is:

vncserver -geometry 2560x1024 -depth 24

NOTE: that when we say display, we mean the physical screen that is on the monitor of remote desktop. However, the pixels of that display are stored in memory, and the monitor is just displaying whatever is stored in that memory. So, we can have another display which has pixels stored in memory only and doesn't go to any monitor. This is called a "virtual display". VNC allows these virtual displays to be created on the server machine, and then be accessed using VNC viewer. Thus we can have 10's of display on a single server desktop, where one of them is real display connected to physical monitor, while all others are all virtual displays.

Headless servers or servers which don't have any monitor connected, don't start the gui program for the display. In such cases, VNC server has nothing to display since it always shows the physical display by default. So, in turn VNC server program doesn't start at startup. in such cases, we start VNC server by logging into the server machine via ssh. Then VNC server creates a virtual display and this virtual display can be seen via a VNC client.

On the top center of VNC session, we have a way to kill VNC or set many options. Look thru them, if you need to set anything else.For ex, if you have 2 monitors, and it's not working, try setting "UseAllMonitors to true" over there.

NOTE: we have .vnc dir in the home dir. Inside this dir, is a config file, which controls how the vnc desktop should look. To get full screen extended on desktop,  we add these lines to config file, so that we don't have to type it every time on cmd line:
-geometry 2560x1024
-depth 24

Guest Access: In VNC, allowing guest acces to others is easy. Steps:

  • Run "vncconfig &" on cmd line. This cmd has to be run on lindesk terminal and not on lsf terminal
  • On pop up box, click commands->options. On new pop up box, choose advanced.
  • Change guest access to "Interactive" and click apply.
  • On main pop-up box, if we click on options, we should see a "tick mark" on Guest Login. If not, tick that by clicking.
  • Now, anyone can connect using login "guest" and no password.
  • When user requests access, a new box appears on bottom. click on "accept" to allow guest access to your vnc m/c.

 

Putty:

 If we are on a windows machine, and don't have terminal to connect to, we can use a program called Putty, that supports a lot of protocols as ssh, ftp, etc. It has a GUI interface, and is a lot easier to use.

First download Putty. Then Use Putty to SSH to the above machine. That brings up a terminal on remote machine to which you work in usual way. When done, log out of Putty and close the window.


-----------------------------------------------------

 

ALL JUNK BELOW. NEED TO MOVE ELSEWHERE FIXME ???

LSF:


Any jobs can now be run only in lsf machine. So open xterm on lsf
Open Xterm on an LSF machine: bsub \-Is \-R "linux&&bit64" "xterm" & => OBSELETE
Open Konsole on an LSF machine: bsub \-Is \-R "linux&&bit64" "konsole" & => OBSELETE
Open Konsole on an LSF machine on RHEL6 OS: bsub \-Is \-R "select[ws60]" "konsole" & => use ws40 for RHEL4 (ws60 is latest). ws60 provides latest AME tools.
Open Konsole on an LSF machine on SUSE11 OS: bsub \-Is \-R "select[sles11]" "konsole" & => this was needed to get latest AME tools, but not anymore. SUSE not used anymore
Run <tool_name> -ame on both OS to see which gives you latest tools. Some newer versions may be avilable on 1 OS and not on other.

OS for Artisan:
Artisan 5.2.1 and earlier will only run on the legacy SuSE11 OS.
Artisan 5.3 will run on both SuSE11 and RHEL6.
The upcoming Artisan 5.4 will only run on RHEL6.

Run icfb on suse m/c: bsub -R "select[sles11]" -Is icfb -artisan-2.91p1 &

NOTE: to get around check and save issues, run icfb on suse m/c: icfb -artisan-5.2.1


For LSF jobs submitted, if we want to know what OS job got submitted on, look in the log file (i.e irun.log) to find name of lsf m/c. Then run:
ex: /home/kagrawal/ > lshosts machine1.com => last 2 RHS entries show OS

HOST_NAME      type    model  cpuf ncpus maxmem maxswp server RESOURCES
dlewz2732.d LIN_X64 p4x_3400 417.0    12 262047M 262145M    Yes
(bit64 cs dc X64 linux srvClass01 maxmem32G linux26 maxmem64G p4x maxmem128G warm maxmem256G sles suse sles11p2 !sles11) => OS is sles11.2
#Preventing jobs from getting killed in lsf:
jobexclude --add <jobid> => to add a job
jobexclude --list => to list all added jobs


#snapshot
In any dir, there is .snapshot dir, within which is there are dir with timestamp. Just cd into appr dir, and cp stuff that is to be retrieved.

dssc cmds:
---------
dssc -help => lsits all options
dssc <cmd_name> -help => lists syntax of a specific cmd

checkin:
checkin for 1st tme: dssc ci -new <file/dir> -com "comments_here" -rec => -rec needed for recursive
checkin a file after editing with lock: dssc ci <filename>
dssc ls -report status -rec => shows revs of all checked out files/dir (current rev vs .)
dssc diff "file1.v" "file1.v;Latest" => diffs b/w current modified checked out file against one in repository.
dssc retire -force => to completely remove it from database.
dssc ci -new <file/dir> -skip => use ci with -skip after retiring a file in db. Else, old retired file will be checked in.

checkout:
checkout all files recursively in read mode: dssc pop -rec => does it starting from current dir
checkout a file in readmode: dssc co <filename>
checkout a file in editmode with lock: dssc co -lock <filename>
checkin a file after editing with lock: dssc ci <filename>

dssc cancel:
dssc cancel -force <filename> => this is to cancel checked out file (even with edits), and to repop with original version.
checkout a file in editmode without lock: dssc co -get <filename> => This gives unlocked copy which can be modified. Do a chmod to 664 or 755.
Then to populate the original file (and discard the current modified file), do:
dssc pop -force <filename>
dssc unlock <filename> => To unlock files (i.e remove lock). This can be useful when files can't be checked out or something else gone bad.

C++ programming lang:

C++ is backward compatible with C, that means you can use all your C code in your C++ pgm, and pgm will still compile fine. So, you already can write C++ pgm, if you know C. Infact all C pgms that you have can be renamed as C++. However, a lot of object oriented features were added in C++, which programmers take advantage of, by modifying existing C code. So, C++ allows you to take incremental steps from a C pgm. All C functions like printf, malloc, etc are available in C++ too.


http://www.learncpp.com/

sample pgm: hello.cpp (C++ files can also be named as .cxx, it's an old style extension that's still used)
----
#include <iostream> //needed for std io func, newer system header files do not have .h extension
#include "myfile.h" //for user defined header files, use " "

void printA(int x, int y) //each func needs to be defined separately
{
    std::cout << "A" << x << y << std::endl; //std:: prefix says that cout is in std namespace
}
 //If func printA is defined after main(), then we need to do forward declaration
void printA(int x, int y); //using func prototype for forward declaration. also used if printA is defined in separate file by itself

// Definition of main()
int main()
{
  int a;
  std::cout << "Enter number " ; //cout to print out on screen. << indicates RHS is transferred to LHS. string is transffered to cout
  std::cin >> a; //cin to take i/p from screen. >> indicates LHS is transferred to RHS. cin transfers val to a
  std::cout << "Hello World num is " << a << std::endl; //endl to put newline. multiple prints can be in same stmt as long as separated by <<
    std::cout << "Starting main()" << std::endl;
    printA(1,a);
    std::cout << "Ending main()" << std::endl;
    return 0; //any +ve num returned means error
}


complie C++:

g++ ~/hello.cpp
execute: ./a.out

Enter number 1
Hello World num is 1
Starting main()
A 1 1
Ending main()

------------------------

Keywords : C++ reserves a set of 84 words (as of C++14) for its own use.
ex: char, int, for, case, while, struct, void, ...

identifiers: The name of a variable, function, type, or other kind of object in C++ is called an identifier. The identifier can only be composed of letters (lower or upper case), numbers, and the underscore character. identifiers are case sensitive

Literals: A literal is a fixed value that has been inserted (hardcoded) directly into the source code, such as 5, or 3.14159. Literals always evaluate to themselves.

operands: Literals, variables, and function calls that return values are all known as operands.

operators: Operators tell the expression how to combine one or more operands to produce a new result.

data types:
boolean: bool (true or false). true is stored as int 1, false as int 0
character: char, char16_t(C++11 only), char32_t(C++11 only). char16_t and char32_t store char in 16 or 32 bit as UTF-16 or UTF-32 Unicode char. ex: 'c'. char stored as 1 byte int (usually signed). Since char is 8 bits, ASCII char numbers are b/w 0 to 127. some char from 0 to 31 are escape char, \n=newline, \t=tab, char code 27 is escape \.
floating point: float, double, long double. signed by default
integer: short, int, long, long long (C++11 only). signed by default (avoid unsigned)
void: for functions that do not take any param or return a value. In C++, empty param are allowed
 ex: int Value(void) { ...} same as int Value() {...}
sizeof(char) => returns size of char in bytes. C++ gurantees min size of each data type, actual size may be bigger. int is min 2 bytes, while float is min 4 bytes. Fixed size int, etc were defined later in C++ inside std namespace. i.e int8_t, uint8_t, int16_t, ...

3 ways to init a var
A. int nval = 5; bool b1=true; //copy initialization
B. int nval(5); //direct initialization
C. int nval{5}; //uniform initialization, works for all data types, but only with C++11. Note: curly braces instead of circle brackets. recommended to use this style.
 int value{}; // default initialization to 0

var assgn (not init):
int nValue;
nValue = 5; // copy assignment (no way to do direct or uniform assignment)
const double gravity { 9.8 }; => assigns const val. can't be changed
const int maxNameLength { 30 }; => assigned const 30
-----------

preprocessor:
ex: #define NUM 7
ex: #ifdef PRINT_J std::cout << "joe"; #endif
ex: header guards
#ifndef SQUARE_H
#define SQUARE_H
....
#endif

---------
namespace:
sample pgm:
ex: constants.h
namespace constants
{
    const double pi(3.14159);
    const double avogadro(6.0221413e23);
    // ... other related constants
}
ex: myfile.cpp
#include "constants.h"
double circumference = 2 * radius * constants::pi;

-------------

C programming lang: Most popular language of last 50 years. Must learn language which can be used to write any simple or complex pgm


C library ref guide: http://www.acm.uiuc.edu/webmonkeys/book/c_guide/index.html

C language has it's own syntax, which may not seem very easy for a person who is used to more modern languages like python. We'll start with very basic "Hello World" pgm which prints "Hello World" on screen. This pgm is explained under "gcc" section, but I'm repeating it here. Name the pgm hello.c

#include <stdio.h> //include files: explained under gcc article

int main (void) { //main function explained below

  printf ("Hello, world!\n");

  return 0;

}

 
main function: Every C pgm needs a main function. This is where the pgm starts executing from line by line.

arguments to main() function are optional. argc and argv variables store the parameters passed in cmd line of the shell when running the pgm.On POSIX compliant systems, char *envp[] contains vector of program's environment var (i.e SHELL environment var that are passed to the program

argc=num of arguments in cmd line including cmd name,

argv[]= vector storing arguments in cmd line, argv[0]=cmd_name (1st argument), argv[1]=2nd argument (1st option), and so on

envp[] = vector storing ENIRONMENT variables

argc, argv[] and envp[] can be any names, i.e main(int cnt, char * arr_option[], char *arr_env[]); is equally valid, though usimg argc, argv and envp is standard

ex: a.out 1 myname 17cd => argc=4 (4 arguments on cmd line), argv[] = {"a.out"  "12", "myname", "17cd" }; Note that argv is an array of strings(char), so even numbers are stored as string. Char strings "12" can be converted to numbers using atol() for 64 bit int conversion or atoi() for 32 bit. i.e y=atoi(argv[1]); => This will assign y to integer 12. Plain casting using (int) will not work, as (int)"12" will convert string 12 to integer which will be ascii code of 1,2,\n converted to integer.
 
int main (int argc, char *argv[]) { // int before main refers to the return value of main. In early days of C, there was no int before main as this was implied. Today, this is considered to be an error (even if main doesn't return any value).

int main (void) { => no arguments to main
...
exit (1); //return value from int. Can also do: return 1;
}

Compile C pgm: We can't run C pgm directly by typing hello.c on terminal. We need to compile it first, which generates an executable "a.out", and then run "a.out" on terminal.To compile above pgm, we use gcc compiler. Type below cmd on terminal. Look in "gcc" section for details.

gcc hello.c => generates a new file a.out. Now type ./a.out on terminal, and it runs the pgm

Syntax of C pgm:

1. comments start with // for single line comment, For multi line comment, use /* comment */

2. Each stmt ends with semicolon ;. We don't need semicolons for blocks

3. main() function is needed. We can define other functions as needed.

4. All variables need to defined before using them. We define the var and specify their type. Types are explained below.

5. For loops, if-else,etc are supported. Many reserved keywords are defined for these, and they can't be used as variable names,

Std Input/Output functions:

printf and scanf are 2 std functions that are going to be used the most. They are inbuilt library functions in C programming language which are available in C library by default. These functions are declared and related macros are defined in “stdio.h” which is a header file in C language. That's why we have to include “stdio.h” file.

1. printf: printf() function is used to print the (“character, string, float, integer, octal and hexadecimal values”) onto the output screen.

ex:

int var1=5; // var1 used in printf below. We define it to be of integer type and assign it a value of 5.

printf ("Value is = %d \n",var1); // %d specifies that var1 is int type, and should be printed in decimal format. var1 needs to be defined before it can be used. var1 is put outside " .. ", while %d is inside " ...". \n is used to print a newline.

2. scanf: scanf() function is used to read character, string, numeric data from keyboard.

ex:

char ch; //var ch is defined of type "character", and is used to stre the i/p entered by the user.

printf("Enter any character \n"); //This printf is same as before. We can't print anything from within scanf. It can only take input. So, we usually precede scanf with printf.

scanf("%c", &ch); //Same as in printf, we have to specify the type of i/p. Here %c says it's character type, and whatever character user enters on prompt is stored in var "ch". Here var "ch" has to be defined as a "char type" before using it. We use an & with ch. That is needed, and will be xplained in pointer section below. No & is needed for var in printf function.



Type:  In C pgm, we have to define data type of all the variables, else C compiler will error out. These are 2 categories of data types for any var. Primary data types and derived data types
-----

I. Primary data type: These are fundamental data types in C namely integer(int), floating point(float), character(char) and void.
arithmetic type: 4 basic types: char, int, float, double and 4 optional type specifiers (signed, unsigned, short, long). void is a data type for nothing.

1. char: 8 bits.
 char, signed char, unsigned char => signed char stored as signed byte. If we use "char" to store character then ascii code for that character is stored. In that case signed or unsigned doesn't matter. However, if we use char to store 8 bit number, then signed unsigned matters, as it represents 8 bit signed or unsigned integer. As "int" stores integers in 16 bit or larger, the only way to store 8 bit integers is by using signed/unsigned char. signed char is from -128 to +127, while unsigned char is from 0 to +255.
 Normal char (no signed/unsigned) are represented in ASCII codes. See in ASCII part of "bash shell scripting language" section.

2. int: 16 bits to 64 bits.
 A. short integer type. atleast 16 bits in size => short, short int, signed short, signed short int, unsigned short, unsigned short int.
 B. basic integer type. atleast 16 bits in size => int, signed, signed int, unsigned, unsigned int. usually 32 bits.
 C. long integer type. atleast 32 bits in size => long, long int, signed long, signed long int, unsigned long, unsigned long int. usually 64 bits, but on embedded arm uP, these are 32 bits.
 D. long long integer type. atleast 64 bits in size => long long, long long int, signed long long, signed long long int, unsigned long long, unsigned long long int. Usually 128 bits, but on embedded arm uP, these are 64 bits.

3. float: IEEE 754 single precision floating point format (32 bit: 1 sign bit, 8 exponent bit and 24 significand precision(23 explicitly stored since 1 bit is hidden)).

4. double: IEEE 754 double precision floating point format (64 bit: 1 sign bit, 11 exponent bit and 53 significand precision(52 explicitly stored since 1 bit is hidden)). "double double" is extended precision floating-point type. It can be 80 bit floating point format or some non-IEEE format.
 
NOTE:  The above types don't have particular size as part of C lang, as it's target processor dependent. So, their size is provided via macro constants in two headers: limits.h header defines macros for integer types and float.h header defines macros for floating-point types.
limit.h: (look in http://www.acm.uiuc.edu/webmonkeys/book/c_guide/2.5.html)
-------
In cortex M0, char size is defined in /apps/arm/rvds/4.0-821/RVCT/Data/4.0/400/include/unix/limits.h as follows:
#define CHAR_BIT 8 => Number of bits in a byte. The actual values depend on the implementation, but user should not define these to be lower than what's put here.
#define CHAR_MIN 0 => minimum value for an object of type char
#define CHAR_MAX 255 => maximum value for an object of type char

similarly signed char is defined as -128 to +127 (8 bits), unsigned char is 0 to 255, signed short int or int is -32768 to +32767 (16 bits), signed long int is -2147483648 to +2147483647(32 bits).

float.h: (look in http://www.acm.uiuc.edu/webmonkeys/book/c_guide/2.4.html)
--------
for float(32 bits), double(64 bits), long double(80 bits).
ex: In cortex M0 float.h, we have:
#define FLT_MAX  3.40282347e+38F

Compiler uses these files to determine size of these primary data types. So, the same pgm may compile differently on different m/c if these files have diff values.

II. Derived data types: Derived data types are nothing but primary datatypes grouped together like array, stucture, union and pointer.

Boolean type: _Bool (true/false). It's an unsigned byte. False has value 0 while true has value 1. It can be seen as derived from int.

In cortex M0 stdbool.h, bool is defined as _Bool, true as 1 and false as 0. (for C compilers pre C99 std). C++ has bool type instead of _Bool type (as per C99 std).
#define bool _Bool
#define true 1
#define false 0

pointer type:

Pointer type var are a new class of var that store addr. Addr could have been stored as type int, but a special "pointer" type was declared to store the addr. For every type T (i.e char, int, etc) there exists a type pointer to T. Variables can be declared as being pointers to values of various types, by means of the * type declarator.
eg:
char v; declare var v as of type char. To get addr location of any variable, use &. So, addr of v is &v
char *pv; (can also be written as char* pv; or char * pv;)=> declares a variable "pv" which is a pointer to value of type char. pv doesn't store a char, but rather an addr. That addr stores a char var. *pv refers to contents stored at memory pointed to by this addr (the addr stored in var pv). &pv is the addr of this var pv, just like &v is the addr of var v. Since, var v istores char, it is just 1 byte in length, while var pv stores an addr, which is 32 bits on a 32 bit system. We can initialize pv to any addr, including special value "NULL" (pv=NULL;):
char *pv=0x1000; => assigns pv to addr value 1000. This is similar to char *pv;pv=0x1000; However, this will error out with this msg: "a value of type "int" cannot be used to initialize an entity of type char *". Reason is although C language allows any integer type to be converted to a pointer type, but such conversions cannot be done implicitly; a cast is required explicitly to do this non-portable coversion. so, we have to do: char *pv = (char *)0x1000; => This casts 0x1000 to pointer type pointing to a char var, which is exactly what pv is, so both sides become same type.

char v='a';
char *pv=&v; => assigns pv to addr loc of variable v. If we print various values of v and pv, this is what we might get:

printf ("v=%c, &v=%x, pv=%x, &pv=%x, *pv=%c",v,&v,pv,&pv,*pv); => v and *pv both store char, so we use %c, while pv, &v and &pv store addr, so we use %x

v=a, &v=f98ca5cf, pv=f98ca5cf, &pv=f98ca5c0, *pv just happens to store the char


pv = &v; => assigns addr loc of v to pv. If pv wasn't declared as a pointer, we could not do this.
pv = (char *)0x1000; => pv is assigned addr value of 1000. The addr 0x1000 stores a char type.
*pv = 0; => store 0 in contents stored at addr pointed by pv.

initializing ptr var: We can initialize ptr var to any addr val. However we can also initialize the contents of ptr var via this:

char *p="Name"; => Here p is created as a ptr and values assigned from addr p, p+1 and so on. So, *p='N', *(p+1)='a', and so on to *(p+4)='\n'. *(p+5) is not initialized to anything, and contains garbage value.


Below 2 assignments are used extensively for rd/wrt of reg mem locations:
1. Rd from given mem loc: 0x1000 or "#define addr 0x1000"
data = *((char *)0x1000); => grab contents of addr loc 0x1000 and assign it to data.
2. Wrt to given mem loc: wrt 1 or "0x01" (since char, so 1 byte only, so 0x01. If short int, use "0x0001")
*((char *) 0x1000) = 1; => This stores 0x01 at addr 0x1000 (Note: it is not char '1' as char '1' is ascii value 0x31. Here we cast 0x1000 to type "pointer of type char" and then "*" in front of it refers to contents stored at addr 0x1000. So, we store "1" at that addr. We can omit extra brackets and do: *(char *) 0x1000 = 1; NOTE: if we try to do: *0x1000=1; then we get this error "operand of "*" must be a pointer" as 0x1000 is a int and not a pointer, so explicit conversion is required.

double pointer: When the pointer stores addr of other pointer (instead of storing addr of char, int, etc), it's pointer to a pointer or double pointer

ex:

int **dpv; => here dpv is double ptr to type int. dpv stores addr of pointer pv for ex, where pv pointer points to data tof ype int

int *pv; => pv is the ptr pointing to data type int.

int v; => regular var decalred as int

pv = &v => single ptr pv is assigned addr of var "v".

dpv = &pv; => double ptr dpv is assigned addr of ptr pv. If dpv wasn't declared a double ptr, then &pv couldn't be assigned to dpv. If dpv was declared a single ptr (i.e int *dpv), then dpv could store addr of regular var "v" and not addr of single ptr pv.

use of double ptr: We use it arg of functions, when we want to change the contents of a var which is outside the function, from inside the function.

1. We use it in arg of main func:

ex: int main(int argc, char **argv) => here we could use char **argv or char *argv[]. It represents an array of char sequences.

2. We use it  in arg of regular func:

 

Array: An array is defined as finite ordered collection of homogenous data, stored in contiguous memory locations.

Array types are introduced along with pointers, as in C, arrays are just a pointer to the first object of that data in the memory. In most contexts, array names "decays" to pointers. In simple words, array names are converted to pointers. That's the reason why you can use pointer with the same name as array to manipulate elements of the array.

ex: char p[10] = "Name"; => This declares an array of 10 char type var = p[0] to p[9]. It is initialized to value "Name", so p[0]='N', p[1]='a',..p[4]='\n', and p[5] to p[9] are uninitialized. p[0] to p[9] are stored in continuous mem locations, and *p or p[0] refers to first element of array *(p+1) or p[1] to second element of array and so on. The way compiler translates array is that it stores it as stores "p" as ref addr, and then uses it to figure out p[0] = *p, p[1] = *(p+1) and so on. This is similar to char *p="Name";

ex: printf("ptr: p addr in hex= %x p addr in dec= %d &p= %x &p[0]=%x *p=%c &p[1]=%x *(p+1)=%c\n",p,p,&p,&p[0],*p,&p[1],*(p+1));

o/p => ptr: p addr in hex= 23fccc60 p addr in dec= 603769952 &p= 23fccc60 &p[0]=23fccc60 *p=N &p[1]=23fccc61 *(p+1)=a //as can be seen, p, &p and &p[0] all refer to addr of start of array p. We can use any of them to access array p. p is used as a ptr to start of array p[9:0].

There are some special cases when an array doesn't decay into a pointer. ex: char str[] = "Name"; Here the size of array is not explicitly specified, so not treated as pointer.

2D array: 2D array are simply double pointers. To see it, consider 1D array, where first element of array is referred via ptr *p. ptr p stores the addr of p[0].  Now consider 2D array. So, p[0][0] to p[0][n] is the first row of 2D array, p[1][0] to p[1][n] is the second row of 2D array and so on. So, to store each row of array, we can have a ptr *p for 1st row, ptr *q for next row and so on. Or we can have an array of ptr *p[] that will store 1st row, 2nd row and so on. So, ptr *p[0] points to 1st element of 1st row, ptr *p[1] points to 1st element of 2nd row and so on. So, it becomes 1D array of pointers, char *p[0], char *p[1] and so on. But any 1D array can be represented by pointers, so p[0], p[1] etc which are pointers can be rep by *p. But *p means p[0], p[1] are char and not ptr. To specify that p[0], p[1] etc are pointers to char, and not char themselves, we specify it as a double ptr, char **p or an array of single pointers as char *p[].

struct:

Struct type is collection of different kind of data which we want stored as single entity. For ex a person's account may contain name, age, income, etc.We can obviously make 3 different types of array: char type for name, int type for age, float type for income, etc. But that would be not easy to manage. However, we can group all these items together in a structure named "account", and then access each of these. That way, we have to create an array of just this "account" type, and it's much easier to manage data.


struct pointer:
------
struct reg { int a; char ch;};
struct reg arr[] = {{1,'s'},{2,'f'}}; //defines arr[0],arr[1] with values of reg type
struct reg *my0 = &arr[0]; //my0 is ptr to type "reg" and is assigned to addr of arr[0] which is of type "reg" too.
struct reg *my1 = &arr[1];

function pointers: http://denniskubes.com/2013/03/22/basics-of-function-pointers-in-c/
-----------------
function names actually refer to addr of func. So, we can have pointers to functions by using func name as addr (no need to use & with func name for addr).
char *pv=&v; => as explained above, this assigns pv to addr loc of variable v. *pv refers to contents of pv.
but for func, we need to have paranthesis around pointer. Also, instead of char, we need to have return val of func, and also need to provide all args of func (If no args, we can write "void" or leave it empty). i.e
void (*FuncPtr)() = HelloFunc; //Hellofunc is function defined with no i/p or o/p. OR
void (*FuncPtr)(void) = HelloFunc;
or:
void (*FuncPtr)(void); => define the ptr function
FuncPtr = HelloFunc; => assign ptr to addr of HelloFunc.

Now to reference any pointer value, we use *pv to read contents. (a=*pv reads contents of pv and stores in a). For function, we do the same, but we need to have *pv inside paranthesis to indicate it's a func. Also, we need (), since () operator is needed to call a function (with args in it if any). i.e
(*FuncPtr)(); //This calls the func HelloFunc() => equiv to calling HelloFunc(); directly.
FuncPtr(); //This also works and is exactly equiv to above line. This is since a func name (label) is converted to a pointer to itself. This means that func names can be used where function pointers are required as input. &func and *func are same and refer to func name, which is a pointer to itself. So, instead of using &func or *func, we should use func or func() directly

ex: (with args)
int (*FuncCalcPtr)(int, int) = FuncCalc; //or *FuncCalc, &FuncCalc, **FuncCalc all are same. FuncCalc fn is subtracts 2 int and returns an int.
int y = (*FuncCalcPtr)(10,2); //or FuncCalcPtr(10,2) is the same. returns 10-2=8, and stores it in y.

Func ptr can also be used as parameters to pass into another func. This is primary use of func ptr. ex:
int domath(int (*mathop)(int, int), int x, int y) {
  return (*mathop)(x, y);
}
in  main, do: int a = domath(add, 10, 2); //this calls domath func with ptr to "add" func.

typedef with pointers:
----
Above ex needed extra typing everytime fn ptr was defined. typedef can simplify this.
ex: Using typedef with pointers:
typedef int *intptr; => newptr is new alias with pointer type int*. "typedef int dist" creates "dist" as a synonym for int. similarly "intptr" is a synonym for "pointer pointing to int"
intptr ptr; => this defines a var "ptr" with type int. So, ptr is a pointer which can point to a memory with int type. We could have also written it as "int *ptr".

ex: Using typedef with fn pointers:
typedef int (*FuncCalcPtr_def)(int, int); => creates "FuncCalcPtr_def" as a synonym for a pointer to a fn of 2 int args that returns an int
FuncCalcPtr_def FuncCalcPtr; => this defines a var "FuncCalcPtr" with type "FuncCalcPtr_def", which is a ptr to fn. We could have written this as "int (*FuncCalcPtr)(int, int);

--------------------
size type:  http://www.acm.uiuc.edu/webmonkeys/book/c_guide/2.11.html
----------
size_t and ptrdiff_t were defined as separate type, since they are mem related. existing arithmetic types were deemed insufficient, since their size is defined according to the target processor's arithmetic capabilities, not the memory capabilities, such as available address space. Both of these types are defined in the stddef.h header as "typedef size_t", "typedef ptrdiff_t".

size_t: used to represent the size of any object (including arrays) in the particular implementation. It is used as the return type of the sizeof operator and is unsigned int.
ptrdiff_t:  used to represent the difference between pointers.

extended integer data type: *_t.
-------------------
to make code portable across diff OS, since existing int type have various sizes depending on system. The new types are especially useful in embedded environments where hardware supports usually only several types and that support varies from system to system. For ex: int N; may be 16 bit with certain complier/processor, while it may be 32 bit with other. Generally we don't care, but if these are being used in structure (as in MMIO), and we use a pointer to refer to various elements of that structure, then size of int does matter. One solution is to define new type as this:
typedef unsigned short uint16; => Here, we still need to know size of char,short,int,long on that complier/processor and modify this defn as needed.

an example depicting this problem is this:
typedef struct {
 volatile uint16_t CTRL1;           // control register
 volatile uint16_t CTRL2; }  CWT_TypeDef;
#define CWT                     ((volatile CWT_TypeDef*)  0x50000000  

Now in main.c, we do:
CWT->CTRL1=0x0012; CWT->CTRL2=0xFF55; => since compiler understands uint16_t to be of 2 bytes, it adds 2 to base addr to store CTRL2.
STRH     r0,[r1,#0];   => stores 0x0012 at base addr
STRH     r0,[r1,#0x2]; => stores 0xFF55 at base_addr+2

However, if we tried to use the same compiled code on some arch which had "unsigned short" implemented as 32 bits, then this code will incorrectly wrt 0x0012 into lower 2 bytes of CTRL1 and then 0xFF55 into upper 2 bytes of CTRL1. CTRL2 will not get written. In order to get rid of this problem, we have to change typedef in stdint.h to the correct 16bit integer and then we don't need to change our C code anywhere else. We recompile and generate correct binary for new arch. It saved us changing code from multiple places.

C99 std of ANSI-C, defined all additional data types as ending in _t, and user is asked not to define new types ending in _t. All new types are to be defined in inttypes.h header and also in stdint.h header by the compiler vendors. We can define types for exact width, least/max width type, etc. Exact width integer types are guaranteed to have the same number N of bits across all implementations. Included only if it is available in the implementation = intN_t, uintN_t. eg: uint8_t = unsigned 8-bit, uint32_t, etc.
sizeof() function can be used to find out the size of int, short, uint32_t, etc.

In cortex M0 stdint.h, int16_t is defined as type of "signed short int" =>  typedef signed short int int16_t;
similarly for uint8_t: typedef unsigned char uint8_t; => Note that in C, integers are represented in 16 bits or larger, so the only way to rep integer in 8 bits is by using signed/unsigned char.

---------------------
keywords & variables: http://www.acm.uiuc.edu/webmonkeys/book/c_guide/1.2.html#variables
---------------------
1. keywords: reserved keywords that can't be used as a variable identifier. ex: for, char, const, extern, etc.
---------
char short int long float double short signed unsigned => type and type specifier   
void
volatile const => type qualifier


2. variables: used to store values of a particular type
--------------------
names of identifiers: The use of two underscores (`__') in identifiers is reserved for the compiler's internal use according to the ANSI-C standard. Underscores (`_') are often used in names of library functions (such as "_main" and "_exit") and are reserved for libraries. In order to avoid collisions, do not begin an identifier with an underscore. Having __ both before and after variable name (eg __Symbol__) almost gurantees that there would be no name collision, as such identifiers with double underscores are extremely rare in user code.

A variable is defined by the following: <storage-class-specifier> <type-qualifier> <type-specifier> <type> variable-names,...
ex: extern const volatile unsigned long int rt_clk; => defines real time clk variable rt_clk

I. storage-class-specifier: storage class reflects data's lifespan during program execution.
1. typedef: The symbol name "variable-name" becomes a type-specifier of type "type-specifier". No variable is created.
ex: typedef long int mytype_t; => declares a new type mytype_t to be long int. From here on, we can use mytype_t instead of long int.
typedef most commonly used with struct to reduce cumbersome typing.
ex:
struct MyStruct {
    int data1;
    char data2;
};
with no typedef, we define var "a" of type Mystruct struct as follows: struct MyStruct a;
however with typedef, we can just define a new type as follows: typedef struct my_struct newtype;
or we can directly do typedef with struct defn as follows:
typedef struct  MyStruct {  
    int data1;
    char data2;
} newtype;
then we can define var "a" as being of type "newtype" as follows: newtype a;

2. extern: Indicates that the variable is defined outside of the current file. This brings the variables scope into the current scope. No variable is created.
3. static (permanent): Causes a variable that is defined within a function to be preserved in subsequent calls to the function. Variables declared outside the body of any function have global scope and static duration (for ex var declared outside main() are static, as they are not within any function). Although initial values may be assigned to global var, these are usually uninit. Since main() itself is a function, all var defined in it are local and auto, so we use "static" for these var to make them permanent. variables decalred outside main() are global for all functions in that file. static var are not released from mem on exit of function, so they consume mem space.
NOTE: we sometimes define function itself as "static".  In C, a static function is not visible outside of its translation unit, which is the object file it is compiled into. In other words, making a function static limits its scope. You can think of a static function as being "private" to its *.c file.
4. auto (temporary): Causes a local variable to have a local lifetime (default). Any variables declared within body of a function, including main(), have local scope and auto duration.
5. register: Requests that the variable be accessed as quickly as possible. This request is not guaranteed. Normally, the variable's value is kept within a CPU register for maximum speed.

II. type-qualifier: any declaration, inlcuding those of variables, struct/union, enum, etc can also have type-qualifier (volatile, auto) which qualifies the decl, instead of specifying it.
1. volatile: added to C pgm later. It causes the value to be fetched from memory every time it's referenced. It tells the compiler that the object is subject to sudden change for reasons which cannot be predicted from a study of the program itself, and forces every reference to such an object to be a genuine reference. This is used for defining variables stored in peripherals, so that uP doesn't read the variable from the register, which it might have stored a while back.
ex: volatile int j;

2. const: const means that something is not modifiable, so a data object that is declared with const as a part of its type specification must not be assigned to in any way during the run of a program.
ex: const int ci = 123; => declares a simple constant ci which always has a value of 123.
ex: const int *cpi; => declares a pointer "cpi" to a constant. cpi is an ordinary, modifiable pointer, but the thing that it points to must not be modified. comipler will check that cpi never points to something whose value changed.
ex: int *const cpi => declares a pointer "cpi" which is constant. It means that cpi is not to be modified, although whatever it points to can be modified \ the pointer is constant, not the thing that it points to.

III. type-specifier: void, all arithmetic types, boolean type, struct, etc.

3. enumerated tags:
-----------------

4. Arrays:
--------
array is defined as: <type-of-array> <name-of-array> [<number of elements in array>];
ex: int arr[10] => defines an array of 10 integers. Array elements are arr[0]. arr[1], ...  arr[9]. Each integer is 4 bytes (let's assume), so total size of array=4*10=40 bytes.

to initialize arrays:
we can either use for loop in main pgm, or init it at time of declaration.
int arr[] = {'1','2','3','4','5'}; => this init an array of 5 integers as follows: arr[0]=1, arr[1]=2 and so on. NOTE: there is no need to mention any value in the subscripts []. The size will automatically be calculated from the number of values. In this case, the size will be 5.
to init array with string 2 ways:
A. char arr[] = {'c','o','d','e','\0'};
B. char arr[] = "code"; => equiv to ex A above. We don't need an explicit null char here, since double quotes do that for us.

To access values in an array:
int j=arr[2]; => assigns j to arr[2] value which is 3.

We can also define array of structures, as well as array elements within structures.

5. structures and union:
----------------------
struct st{
    int a;
    char c[5];
};
int main()
{
    struct st st_arr[3]; // Declare an array of 3 structure objects
    struct st st_any[] = { {0,'c'},{1,'f'}}; //declares an array of 2 struct obj with values assigned    
    struct st st_obj0; // first structure object
    st_obj0.a = 0;
    st_obj0.c = 'a';
}

6. const:

7. strings: simply an array of characters encapsulated in double quotes. At the end of the string a null character is appended.
ex: char x="\x41" or "A" are the same string. x[0]=A, x[1]=null character.

8. define, ifdef: preprocessor that are remved by compiler depending on directive
#define NEW 0
#ifdef NEW => since NEW is defined this portion is kept by compiler
#define var ab
#else => this portion is removed by compiler
#define var cd
#endif

ex: this very commonly used in defines, so that we don't redefine something in multiple files
#ifndef CHAR_T => this piece of code can be placed in multiple files, but it will be compiled only from 1 file.
#define CHAR_T 0x45
#end
 
-----------------------------------

random number gen:
------------------
srand((unsigned) time(&t)); => inits rand num gen with time (in sec since epoch)
rand() % 50; => generates rand num b/w 0 to 49 using above seed. otherwise rand() will always gen same rand num seq since it will always use same seed (if we don't use srand).

More Articles ...

  1. python
  2. perl
  3. PnR ICC
  4. PnR VDI