fan-out , Buffer, Open Collector, and Tri-state

Fan-out

Fan-out is a term that defines the maximum number of digital inputs that the output of a single logic gate can feed. Most transistor-transistor logic ( TTL ) gates can feed up to 10 other digital gates or devices. Thus, a typical TTL gate has a fan-out of 10.

In some digital systems, it is necessary for a single TTL logic gate to drive more than 10 other gates or devices. When this is the case, a device called a buffer can be used between the TTL gate and the multiple devices it must drive. A buffer of this type has a fan-out of 25 to 30. A logical inverter (also called a NOT gate) can serve this function in most digital circuits.

Buffer

  • Two inverter, or NOT, gates connected in “series” so as to invert, then re-invert, a binary bit perform the function of a buffer. Buffer gates merely serve the purpose of signal amplification: taking a “weak” signal source that isn’t capable of sourcing or sinking much current, and boosting the current capacity of the signal so as to be able to drive a load.
  • Buffer circuits are symbolized by a triangle symbol with no inverter “bubble.”
  • Buffers, like inverters, may be made in open-collector output or totem pole output forms

Open Collector OR Open Drain

Tri-state

Three-state logic is a logic used in electronic circuits wherein a third state, the high-impedance state

Three-state logic is used to allow multiple circuits to share the same output or bus lines which may not be capable of listening to more than one device or circuit at a time. In this way, the high-impedance state acts as a selector which blocks out circuits that are not being used. As mentioned, the whole concept of the high-impedance state is to effectively remove the circuit or device’s influence from the rest of the circuit as if it were not connected at all. Putting one device on high-impedance is normally used to prevent a short circuit with the other device directly connected in the same way to the same leads, this also prevents both devices being driven at once since this may lead to unintended output or input and cause the whole circuit to malfunction.

three outputs share same bus you can enable one of them to get its value

Totem Pole

A type of output structure used with integrated circuits in which one transistor drives the output high while another transistor connected below it pulls the output low.

Latch and Flip-Flop

SR Latch

and it’s symbol is

  • The enable input on a multivibrator must be activated for either S or R inputs to have any effect on the output state.
  • This enable input is sometimes labeled “E”, and other times as “EN”.
74ch279

D Latch

  • A D latch is like an S-R latch with only one input: the “D” input. Activating the D input sets the circuit, and de-activating the D input resets the circuit. Of course, this is only if the enable input (E) is activated as well. Otherwise, the output(s) will be latched, unresponsive to the state of the D input.
  • D latches can be used as 1-bit memory circuits, storing either a “high” or a “low” state when disabled, and “reading” new data from the D input when enabled.

Edge-triggered Latches: Flip-Flops

compare timing diagrams for a normal D latch versus one that is edge-triggered:

Regular D latch response
Positive edge triggered D latch response

Implementing this timing function with semiconductor components is actually quite easy, as it exploits the inherent time delay within every logic gate (known as propagation delay). What we do is take an input signal and split it up two ways, then place a gate or a series of gates in one of those signal paths just to delay it a bit, then have both the original signal and its delayed counterpart enter into a two-input gate that outputs a high signal for the brief moment of time that the delayed signal has not yet caught up to the low-to-high change in the non-delayed signal. An example circuit for producing a clock pulse on a low-to-high input signal transition is shown here:

Low to high input signal transition

This circuit may be converted into a negative-edge pulse detector circuit with only a change of the final gate from AND to NOR:

Negative edge pulse detector circuit

The block symbols for flip-flops are slightly different from that of their respective latch counterparts:

The triangle symbol next to the clock inputs tells us that these are edge-triggered devices, and consequently that these are flip-flops rather than latches. The symbols above are positive edge-triggered: that is, they “clock” on the rising edge (low-to-high transition) of the clock signal. Negative edge-triggered devices are symbolized with a bubble on the clock input line:

  • flip-flop is a latch circuit with a “pulse detector” circuit connected to the enable (E) input, so that it is enabled only for a brief moment on either the rising or falling edge of a clock pulse.
  • Pulse detector circuits may be made from time-delay relays for ladder logic applications, or from semiconductor gates (exploiting the phenomenon of propagation delay).

J-K Flip-Flop

Another variation on a theme of bistable multivibrators is the J-K flip-flop. Essentially, this is a modified version of an S-R flip-flop with no “invalid” or “illegal” output state. Look closely at the following diagram to see how this is accomplished:

J K flip flop diagram
Block symbol for J-K flip flops
symbol
  • A J-K flip-flop is nothing more than an S-R flip-flop with an added layer of feedback. This feedback selectively enables one of the two set/reset inputs so that they cannot both carry an active signal to the multivibrator circuit, thus eliminating the invalid condition.
  • When both J and K inputs are activated, and the clock input is pulsed, the outputs (Q and not-Q) will swap states. That is, the circuit will toggle from a set state to a reset state or vice versa.

Asynchronous Flip-Flop Inputs

The normal data inputs to a flip flop (DS and R, or J and K) are referred to as synchronous inputs because they have an effect on the outputs (Q and not-Q) only in step, or in sync, with the clock signal transitions.

These extra inputs that I now bring to your attention are called asynchronous because they can set or reset the flip-flop regardless of the status of the clock signal. Typically, they’re called preset and clear:

Asynchronous flip flops inputs diagram three

When the preset input is activated, the flip-flop will be set (Q=1, not-Q=0) regardless of any of the synchronous inputs or the clock. When the clear input is activated, the flip-flop will be reset (Q=0, not-Q=1), regardless of any of the synchronous inputs or the clock.
So, what happens if both preset and clear inputs are activated? Surprise, surprise: we get an invalid state on the output, where Q and not-Q go to the same state, the same as our old friend, the S-R latch!

  • Asynchronous inputs on a flip-flop have control over the outputs (Q and not-Q) regardless of clock input status.
  • These inputs are called the preset (PRE) and clear (CLR). The preset input drives the flip-flop to a set state while the clear input drives it to a reset state.
  • It is possible to drive the outputs of a J-K flip-flop to an invalid condition using the asynchronous inputs, because all feedback within the multivibrator circuit is overridden.

Moving to Modern C++

Distinguish between () and {} when creating objects

class Widget {
public:
 Widget(int i, bool b); // ctors not declaring
 Widget(int i, double d); // std::initializer_list params
 …
};
Widget w1(10, true); // calls first ctor
Widget w2{10, true}; // also calls first ctor
Widget w3(10, 5.0); // calls second ctor
Widget w4{10, 5.0}; // also calls second ctor
class Widget {
public:
 Widget(int i, bool b); // as before
 Widget(int i, double d); // as before
Widget(std::initializer_list<long double> il); // added
 …
};
Widget w1(10, true); // uses parens and, as before,
 // calls first ctor
Widget w2{10, true}; // uses braces, but now calls
 // std::initializer_list ctor
// (10 and true convert to long double)
Widget w3(10, 5.0); // uses parens and, as before,
 // calls second ctor
Widget w4{10, 5.0}; // uses braces, but now calls
 // std::initializer_list ctor
// (10 and 5.0 convert to long double)
class Widget {
public:
 Widget(int i, bool b); // as before
 Widget(int i, double d); // as before
 Widget(std::initializer_list<long double> il); // as before
 operator float() const; // convert
 … // to float
};
Widget w5(w4); // uses parens, calls copy ctor
Widget w6{w4}; // uses braces, calls std::initializer_list ctor (w4 converts to float, and float converts to long double)
Widget w7(std::move(w4)); // uses parens, calls move ctor
Widget w8{std::move(w4)}; // uses braces, calls std::initializer_list ctor (for same reason as w6)
class Widget {
public:
 Widget(int i, bool b); // as before
 Widget(int i, double d); // as before
 // std::initializer_list element type is now std::string
 Widget(std::initializer_list<std::string> il);
 … // no implicit
}; // conversion funcs
Widget w1(10, true); // uses parens, still calls first ctor
Widget w2{10, true}; // uses braces, now calls first ctor
Widget w3(10, 5.0); // uses parens, still calls second ctor
Widget w4{10, 5.0}; // uses braces, now calls second ctor
class Widget {
public:
 Widget(); // default ctor
 Widget(std::initializer_list<int> il); // std::initializer
 // _list ctor
 … // no implicit
}; // conversion funcs
Widget w1; // calls default ctor
Widget w2{}; // also calls default ctor
Widget w3(); // most vexing parse! declares a function!
Widget w4({}); // calls std::initializer_list ctor with empty list
Widget w5{{}}; // ditto
std::vector<int> v1(10, 20); // use non-std::initializer_list
 // ctor: create 10-element
// std::vector, all elements have
// value of 20
std::vector<int> v2{10, 20}; // use std::initializer_list ctor:
 // create 2-element std::vector,
// element values are 10 and 20
template<typename T, // type of object to create
 typename... Ts> // types of arguments to use
void doSomeWork(Ts&&... params)
{
 create local T object from params...
 …
}
T localObject(std::forward<Ts>(params)...); // using parens
T localObject{std::forward<Ts>(params)...}; // using braces

Prefer nullptr to 0 and NULL

void f(int); // three overloads of f
void f(bool);
void f(void*);
f(0); // calls f(int), not f(void*)
f(NULL); // might not compile, but typically calls
 // f(int). Never calls f(void*)
f(nullptr); // calls f(void*) overload

Prefer alias declarations to typedefs

Avoiding such medical tragedies is easy. Introduce a typedef:

typedef std::unique_ptr> UPtrMapSS;

But typedefs are soooo C++98. They work in C++11, sure, but C++11 also offers alias declarations:

using UPtrMapSS = std::unique_ptr>;
// FP is a synonym for a pointer to a function taking an int and
// a const std::string& and returning nothing
typedef void (*FP)(int, const std::string&); // typedef
// same meaning as above
using FP = void (*)(int, const std::string&); // alias
 // declaration
template<typename T> // MyAllocList<T>
using MyAllocList = std::list<T, MyAlloc<T>>; // is synonym for
 // std::list<T,
// MyAlloc<T>>
MyAllocList<Widget> lw; // client code

template<typename T> // MyAllocList<T>::type
struct MyAllocList { // is synonym for
 typedef std::list<T, MyAlloc<T>> type; // std::list<T,
}; // MyAlloc<T>>
MyAllocList<Widget>::type lw; // client code

• typedefs don’t support templatization, but alias declarations do.
• Alias templates avoid the “::type” suffix and, in templates, the “typename”
prefix often required to refer to typedefs

template <class T>
using remove_const_t = typename remove_const<T>::type;
template <class T>
using remove_reference_t = typename remove_reference<T>::type;
template <class T>
using add_lvalue_reference_t = typename add_lvalue_reference<T>::type;

Prefer scoped enums to unscoped enums

enum Color { black, white, red }; // black, white, red are in same scope as Color
auto white = false; // error! white already declared in this scope

enum class Color { black, white, red }; // black, white, red are scoped to Color
auto white = false; // fine, no other
Color c = white; // error! no enumerator named "white" is in this scope
Color c = Color::white; // fine
auto c = Color::white; // also fine (and in accord with Item 5's advice)
enum Color { black, white, red }; // unscoped enum
std::vector<std::size_t> // func. returning
 primeFactors(std::size_t x); // prime factors of x
Color c = red;
…
if (c < 14.5) { // compare Color to double (!)
 auto factors = // compute prime factors
 primeFactors(c); // of a Color (!)
 …
}
enum class Color { black, white, red }; // enum is now scoped
Color c = Color::red; // as before, but
… // with scope qualifier
if (c < 14.5) { // error! can't compare Color and double
auto factors = // error! can't pass Color to
 primeFactors(c); // function expecting std::size_t
 …
}
if (static_cast<double>(c) < 14.5) { // odd code, but
 // it's valid
 auto factors = // suspect, but
 primeFactors(static_cast<std::size_t>(c)); // it compiles
 …
}

Prefer deleted functions to private undefined ones.

template <class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base {
public:
 …
private:
 basic_ios(const basic_ios& ); // not defined
 basic_ios& operator=(const basic_ios&); // not defined
};
//instead prefer to use delete
template <class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base {
public:
 …
 basic_ios(const basic_ios& ) = delete;
 basic_ios& operator=(const basic_ios&) = delete;
 …
};
void fun(int k)
void fun(char)=delete;
void fun(double)=delete;
fun(12);//ok
fun('a');//error
fun(4.5);//error
class Widget {
public:
 …
 template<typename T>
 void processPointer(T* ptr)
 { … }
 …
};
template<> // still
void Widget::processPointer<void>(void*) = delete; // public, but deleted

Deducing Types

Template

template<typename T>
void f(T& param); // param is a reference
//and we have these variable declarations,
int x = 27; // x is an int
const int cx = x; // cx is a const int
const int& rx = x; // rx is a reference to x as a const int
//the deduced types for param and T in various calls are as follows:
f(x); // T is int, param's type is int&
f(cx); // T is const int,
 // param's type is const int&
f(rx); // T is const int,
 // param's type is const int&

universal reference

template<typename T>
void f(T&& param); // param is now a universal reference
int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before
f(x); // x is lvalue, so T is int&, param's type is also int&
f(cx); // cx is lvalue, so T is const int&, param's type is also const int&
f(rx); // rx is lvalue, so T is const int&, param's type is also const int&
f(27); // 27 is rvalue, so T is int,param's type is therefore int&&

not pointer or reference

template<typename T>
void f(T param); // param is now passed by value
int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before
f(x); // T's and param's types are both int
f(cx); // T's and param's types are again both int
f(rx); // T's and param's types are still both int
template<typename T>
void f(T param); // param is still passed by value
const char* const ptr = // ptr is const pointer to const object "Fun with pointers";
f(ptr); // pass arg of type const char * const

Array Arguments

template<typename T, std::size_t N> // see info
constexpr std::size_t arraySize(T (&)[N]) noexcept // below on
{
    return N; // and
} 
int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 }; // keyVals has 7 elements
int mappedVals[arraySize(keyVals)]; // so does mappedVals

Auto

auto x = 27; // case 3 (x is neither ptr nor reference)
const auto cx = x; // case 3 (cx isn't either)
const auto& rx = x; // case 1 (rx is a non-universal ref.)
auto&& uref1 = x; // x is int and lvalue,  so uref1's type is int&
auto&& uref2 = cx; // cx is const int and lvalue,  so uref2's type is const int&
auto&& uref3 = 27; // 27 is int and rvalue,  so uref3's type is int&&
const char name[] ="R. N. Briggs"; // name's type is const char[13]
auto arr1 = name; // arr1's type is const char*
auto& arr2 = name; // arr2's type is
void someFunc(int, double); // someFunc is a function;  type is void(int, double)
auto func1 = someFunc; // func1's type is void (*)(int, double)
auto& func2 = someFunc; // func2's type is void (&)(int, double)
auto x5 = { 1, 2, 3.0 }; // error! can't deduce T for std::initializer_list<T>

Decltype

const int i = 0; // decltype(i) is const int
bool f(const Widget& w); // decltype(w) is const Widget& decltype(f) is bool(const Widget&)
struct Point {
 int x, y; // decltype(Point::x) is int
}; // decltype(Point::y) is int
Widget w; // decltype(w) is Widget
if (f(w)) … // decltype(f(w)) is bool
template<typename T> // simplified version of std::vector
class vector {
public:
 …
 T& operator[](std::size_t index);
 …
};
vector<int> v; // decltype(v) is vector<int>
…
if (v[0] == 0) … // decltype(v[0]) is int&
template<typename Container, typename Index> // works, but requires refinement
auto authAndAccess(Container& c, Index i) -> decltype(c[i]) 
{
 authenticateUser();
 return c[i];
}
template<typename Container, typename Index> // C++14;
auto authAndAccess(Container& c, Index i) // not quite
{ // correct
 authenticateUser();
 return c[i]; // return type deduced from c[i]
}
std::deque<int> d;
…
authAndAccess(d, 5) = 10; // authenticate user, return d[5],
 // then assign 10 to it;
// this won't compile!

template<typename Container, typename Index> // C++14; works,but still requires refinement
decltype(auto) 
authAndAccess(Container& c, Index i) 
{ 
 authenticateUser();
 return c[i];
}

Widget w;
const Widget& cw = w;
auto myWidget1 = cw; // auto type deduction: myWidget1's type is Widget
decltype(auto) myWidget2 = cw; // decltype type deduction: myWidget2's type 
    int i=12;
    decltype(i) ii=i;//value
    decltype((i)) iii=i;//reference
    decltype(auto) iiii=i;//reference
decltype(auto) f1()
{
 int x = 0;
 …
 return x; // decltype(x) is int, so f1 returns int
}
decltype(auto) f2()
{
 int x = 0;
 …
 return (x); // decltype((x)) is int&, so f2 returns int&
}

why auto

template<typename It> // algorithm to dwim ("do what I mean")
void dwim(It b, It e) // for all elements in range from
{ // b to e
 while (b != e) {
 typename std::iterator_traits<It>::value_type currValue = *b;
 }
}

and you can use auto instead

template<typename It> // as before
void dwim(It b, It e)
{
 while (b != e) {
 auto currValue = *b;
 …
 }
auto derefUPLess = // comparison func.
 [](const std::unique_ptr<Widget>& p1, // for Widgets
 const std::unique_ptr<Widget>& p2) // pointed to by
 { return *p1 < *p2; }; // std::unique_ptrs
//use auto for all 
auto derefLess = // C++14 comparison
 [](const auto& p1, // function for
 const auto& p2) // values pointed
 { return *p1 < *p2; }; // to by anything
 // pointer-like

CONSTEXPR

Declare functions noexcept if they won’t emit exceptions

Values known during compilation are privileged. They may be placed in read-only memory, for example, and, especially for developers of embedded systems, this can be a feature of considerable importance. Of broader applicability is that integral val‐ ues that are constant and known during compilation can be used in contexts where C++ requires an integral constant expression. Such contexts include specification of array sizes, integral template arguments (including lengths of std::array objects), enumerator values, alignment specifiers, and more. If you want to use a variable for these kinds of things, you certainly want to declare it constexpr, because then com‐ pilers will ensure that it has a compile-time value:

int sz; // non-constexpr variable … 
constexpr auto arraySize1 = sz; // error! sz's value not known at compilation 
std::array data1; // error! same problem 
constexpr auto arraySize2 = 10; // fine, 10 is a compile-time constant
std::array<int, arraySize2> data2; // fine, arraySize2 is constexpr

Note that const doesn’t offer the same guarantee as constexpr, because const
objects need not be initialized with values known during compilation:

int sz; // as before
…
const auto arraySize = sz; // fine, arraySize is const copy of sz
std::array<int, arraySize> data; // error! arraySize's value not known at compilation

Simply put, all constexpr objects are const, but not all const objects are constexpr.
If you want compilers to guarantee that a variable has a value that can beused in contexts requiring compile-time constants, the tool to reach for is constexpr, not const.

constexpr functions can be used in contexts that demand compile-time con‐
stants. If the values of the arguments you pass to a constexpr function in such a context are known during compilation, the result will be computed during compilation. If any of the arguments’ values is not known during compilation,your code will be rejected.

When a constexpr function is called with one or more values that are not
known during compilation, it acts like a normal function, computing its result at runtime. This means you don’t need two functions to perform the same operation, one for compile-time constants and one for all other values. The constexpr function does it all.

constexpr // pow's a constexpr func
int pow(int base, int exp) noexcept // that never throws
{
 … // impl is below
}
constexpr auto numConds = 5; // # of conditions
std::array<int, pow(3, numConds)> results; // results has 3^numConds elements
auto base = readFromDB("base"); // get these values
auto exp = readFromDB("exponent"); // at runtime
auto baseToExp = pow(base, exp); // call pow function at runtime
constexpr int pow(int base, int exp) noexcept
{
 return (exp == 0 ? 1 : base * pow(base, exp - 1));
}

constexpr functions are limited to taking and returning literal types, which essentially means types that can have values determined during compilation. In C++11, all built-in types except void qualify, but user-defined types may be literal, too, because constructors and other member functions may be constexpr:

class Point {
public:
 constexpr Point(double xVal = 0, double yVal = 0) noexcept
 : x(xVal), y(yVal)
 {}
 constexpr double xValue() const noexcept { return x; }
 constexpr double yValue() const noexcept { return y; }
 void setX(double newX) noexcept { x = newX; }
 void setY(double newY) noexcept { y = newY; }
private:
 double x, y;
};
constexpr Point p1(9.4, 27.7); // fine, "runs" constexpr ctor during compilation
constexpr
Point midpoint(const Point& p1, const Point& p2) noexcept
{
 return { (p1.xValue() + p2.xValue()) / 2, // call constexpr
 (p1.yValue() + p2.yValue()) / 2 }; // member funcs
}
constexpr auto mid = midpoint(p1, p2); // init constexpr object w/result of constexpr function

In C++11, two restrictions prevent Point’s member functions setX and setY from being declared constexpr. First, they modify the object they operate on, and in C++11, constexpr member functions are implicitly const. Second, they have void return types, and void isn’t a literal type in C++11. Both these restrictions are lifted in C++14, so in C++14, even Point’s setters can be constexpr:

class Point {
public:
 …
 constexpr void setX(double newX) noexcept // C++14
 { x = newX; }
 constexpr void setY(double newY) noexcept // C++14
 { y = newY; }
 …
};
constexpr Point reflection(const Point& p) noexcept
{
 Point result; // create non-const Point
 result.setX(-p.xValue()); // set its x and y values
 result.setY(-p.yValue());
 return result; // return copy of it
}
constexpr Point p1(9.4, 27.7); // as above
constexpr Point p2(28.8, 5.3);
constexpr auto mid = midpoint(p1, p2);
constexpr auto reflectedMid = // reflectedMid's value is
 reflection(mid); // (-19.1 -16.5) and known  during compilation