Chapter 12: Virtual Platform Construction

Peripheral Modeling: GPIO

Modeling General Purpose Input/Output (GPIO) pins combining TLM registers and discrete SystemC signals.

Peripheral Modeling: GPIO

So far, our Virtual Platform peripherals (RAM, Timer) have communicated entirely through TLM sockets. However, many peripherals interact with the outside world via physical wires. A General Purpose Input/Output (GPIO) peripheral is the perfect example of a bridge between memory-mapped TLM configuration and standard sc_signal hardware pins.

The GPIO Architecture

A standard GPIO peripheral exposes:

  1. TLM Target Socket: To receive configuration (Pin Direction) and data (Output Value) from the CPU via memory-mapped registers.
  2. Standard SystemC Ports (sc_out / sc_in): The actual external hardware pins that toggle high or low.

Complete GPIO Peripheral Example

This complete sc_main model demonstrates an 8-bit GPIO controller. The CPU sets the pin directions (Input or Output) via the DIR register, and drives the pins via the OUT register.

#include <systemc>
#include <tlm>
#include <tlm_utils/simple_target_socket.h>
 
SC_MODULE(GPIO_Controller) {
    tlm_utils::simple_target_socket<GPIO_Controller> socket;
    
    // External hardware pins (8-bit bus)
    sc_core::sc_out<sc_dt::sc_bv<8>> pins_out{"pins_out"};
 
    // Internal Registers
    uint8_t reg_dir = 0x00; // 1 = Output, 0 = Input
    uint8_t reg_out = 0x00; // The logic levels to drive
 
    SC_CTOR(GPIO_Controller) : socket("socket") {
        socket.register_b_transport(this, &GPIO_Controller::b_transport);
        
        // Drive initial state
        SC_THREAD(init_pins);
    }
 
private:
    void init_pins() {
        wait(sc_core::SC_ZERO_TIME);
        update_hardware_pins();
    }
 
    void update_hardware_pins() {
        // Only drive the bits configured as outputs
        sc_dt::sc_bv<8> current_val = reg_out & reg_dir; 
        pins_out.write(current_val);
        
        std::cout << "@" << sc_core::sc_time_stamp() 
                  << " [GPIO] Hardware Pins Driven: " << current_val << std::endl;
    }
 
    void b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay) {
        sc_dt::uint64 addr = trans.get_address();
        unsigned char* ptr = trans.get_data_ptr();
        unsigned int len = trans.get_data_length();
 
        if (len != 1) { // Enforce 8-bit access for this simple peripheral
            trans.set_response_status(tlm::TLM_BURST_ERROR_RESPONSE);
            return;
        }
 
        if (trans.get_command() == tlm::TLM_WRITE_COMMAND) {
            uint8_t val = *ptr;
            if (addr == 0x00) { // DIR Register
                reg_dir = val;
                std::cout << "@" << sc_core::sc_time_stamp() << " [GPIO] DIR Reg = 0x" << std::hex << (int)reg_dir << std::endl;
            } else if (addr == 0x01) { // OUT Register
                reg_out = val;
                std::cout << "@" << sc_core::sc_time_stamp() << " [GPIO] OUT Reg = 0x" << std::hex << (int)reg_out << std::endl;
            }
            // A change in registers triggers a physical pin update
            update_hardware_pins();
        }
 
        delay += sc_core::sc_time(10, sc_core::SC_NS);
        trans.set_response_status(tlm::TLM_OK_RESPONSE);
    }
};
 
int sc_main(int argc, char* argv[]) {
    // 1. Hardware Wires
    sc_core::sc_signal<sc_dt::sc_bv<8>> external_bus("external_bus");
 
    // 2. Instantiate Peripheral
    GPIO_Controller gpio("gpio");
    gpio.pins_out(external_bus); // Bind to external hardware
 
    // 3. Mock CPU Transaction (Configure and Drive)
    tlm::tlm_generic_payload trans;
    sc_core::sc_time delay = sc_core::SC_ZERO_TIME;
    uint8_t data;
 
    // Configure lower 4 bits as Outputs
    data = 0x0F;
    trans.set_command(tlm::TLM_WRITE_COMMAND);
    trans.set_address(0x00);
    trans.set_data_ptr(&data);
    trans.set_data_length(1);
    gpio.socket->b_transport(trans, delay);
 
    // Drive 0xA on the output pins
    data = 0x0A;
    trans.set_address(0x01);
    gpio.socket->b_transport(trans, delay);
 
    sc_core::sc_start();
    return 0;
}

By combining TLM sockets for high-speed software control and standard sc_signal objects for physical interactions, SystemC effectively models the boundary between the processor subsystem and the external printed circuit board.

Comments and Corrections