Initial implementation of simulated flash chips

There is still a lot of work to do in order to make it pretty, but
this is a great start! Chip ID function works.
This commit is contained in:
Doug Brown 2021-07-31 17:06:58 -07:00
parent 2df20a9fa7
commit af171135a3
10 changed files with 940 additions and 30 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/Debug
/Release
.settings/org.eclipse.cdt.core.prefs
CMakeLists.txt.user

View File

@ -23,6 +23,7 @@
*/
#include "board_hw.h"
#include "flash_4mbit.h"
/** Initializes any board hardware-specific stuff
*
@ -30,6 +31,28 @@
void Board_Init(void)
{
// Nothing to do on PC
// TODO: Figure out a way to make this dynamically configurable. But for now...
GPIOPin cs = {GPIOMISC, 4};
GPIOPin oe = {GPIOMISC, 5};
GPIOPin we = {GPIOMISC, 6};
static Flash4MBit ics[4];
for (uint8_t i = 0; i < 4; i++)
{
Flash4MBit_Init(&ics[i], Flash_SST39SF040);
GPIOSim_AddDevice(&ics[i].base);
Flash4MBit_SetControlPins(&ics[i], cs, oe, we);
for (uint8_t j = 0; j < FLASH_4MBIT_DATA_PINS; j++)
{
GPIOPin dataPin = {GPIODATA, i * 8 + j};
Flash4MBit_SetDataPin(&ics[i], j, dataPin);
}
for (uint8_t j = 0; j < FLASH_4MBIT_ADDR_PINS; j++)
{
GPIOPin addrPin = {GPIOADDR, j};
Flash4MBit_SetAddressPin(&ics[i], j, addrPin);
}
}
}
/** Determines if a brownout was detected at startup

496
hal/pc/flash_4mbit.c Normal file
View File

@ -0,0 +1,496 @@
/*
* flash_4mbit.c
*
* Created on: Jul 27, 2021
* Author: Doug
*
* Copyright (C) 2011-2021 Doug Brown
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "flash_4mbit.h"
#include <stdlib.h>
#include <string.h>
// TODO: Can we use a timer after an erase operation begins?
// I'd rather not base the emulation on the number of confirm read cycles
// we get...
static void UpdateGPIOPin(GPIOSimDevice *device, GPIOPin pin, bool high);
static GPIOSimValue ReadGPIOPin(GPIOSimDevice *device, GPIOPin pin);
static void HandleWriteCycle(Flash4MBit *f, uint32_t address, uint8_t data);
static void HandleReadCycle(Flash4MBit *f);
static int8_t DataPinIndex(Flash4MBit *f, GPIOPin pin);
static int8_t AddressPinIndex(Flash4MBit *f, GPIOPin pin);
static bool IsCSPin(Flash4MBit *f, GPIOPin pin);
static bool IsOEPin(Flash4MBit *f, GPIOPin pin);
static bool IsWEPin(Flash4MBit *f, GPIOPin pin);
static uint32_t UnlockAddressMask(Flash4MBit *f);
static uint8_t ManufacturerID(Flash4MBit *f);
static uint8_t DeviceID(Flash4MBit *f);
static void EraseChip(Flash4MBit *f);
static void EraseSector(Flash4MBit *f, uint32_t address);
static const GPIOSimDeviceFunctions functions = {
.drivePin = UpdateGPIOPin,
.readPin = ReadGPIOPin,
};
enum FlashState
{
FlashReading,
FlashUnlocking,
FlashWaitingForCommand,
FlashSoftwareID,
FlashProgrammingByte,
FlashEraseUnlocking1,
FlashEraseUnlocking2,
FlashEraseWaitingForCommand,
FlashReadingWriteOrEraseStatus,
};
/**
* @brief Initializes emulation for a single 4-megabit flash chip
* @param f The flash chip object
* @param t The type of flash chip being emulated
*/
void Flash4MBit_Init(Flash4MBit *f, Flash4MBitDeviceType t)
{
f->base.functions = &functions;
f->type = t;
f->dataInput = 0;
f->address = 0;
f->latchedWriteAddress = 0;
f->csAsserted = false;
f->oeAsserted = false;
f->weAsserted = false;
f->softwareIDActivated = false;
f->state = FlashReading;
f->content = malloc(512*1024);
// TODO: Dealloc content
}
void Flash4MBit_SetControlPins(Flash4MBit *f, GPIOPin cs, GPIOPin oe, GPIOPin we)
{
f->csPin = cs;
f->oePin = oe;
f->wePin = we;
}
void Flash4MBit_SetDataPin(Flash4MBit *f, uint8_t index, GPIOPin pin)
{
if (index < FLASH_4MBIT_DATA_PINS)
{
f->dataPins[index] = pin;
}
}
void Flash4MBit_SetAddressPin(Flash4MBit *f, uint8_t index, GPIOPin pin)
{
if (index < FLASH_4MBIT_ADDR_PINS)
{
f->addressPins[index] = pin;
}
}
static void UpdateGPIOPin(GPIOSimDevice *device, GPIOPin pin, bool high)
{
Flash4MBit *f = (Flash4MBit *)device;
int8_t pinIndex;
if (IsCSPin(f, pin))
{
if (f->csAsserted != !high)
{
f->csAsserted = !high;
// If WE is asserted, latch stuff...
if (f->weAsserted)
{
// Latch the address on the falling edge of CS
if (f->csAsserted)
{
f->latchedWriteAddress = f->address;
}
// Latch the data (and update state) on the rising edge of CS
else
{
HandleWriteCycle(f, f->latchedWriteAddress, f->dataInput);
}
}
else if (f->oeAsserted)
{
if (!f->csAsserted)
{
// Handle a read cycle when CS is deasserted while OE is asserted.
// The data isn't really important; it's more of a state machine update
HandleReadCycle(f);
}
}
}
}
else if (IsOEPin(f, pin))
{
if (f->oeAsserted != !high)
{
f->oeAsserted = !high;
// If CS is asserted, latch stuff...
if (f->csAsserted)
{
if (!f->oeAsserted)
{
// Handle a read cycle when OE is deasserted while CS is asserted.
// The data isn't really important; it's more of a state machine update
HandleReadCycle(f);
}
}
}
}
else if (IsWEPin(f, pin))
{
if (f->weAsserted != !high)
{
f->weAsserted = !high;
// If CS is asserted, latch stuff...
if (f->csAsserted)
{
// Latch the address on the falling edge of WE
if (f->weAsserted)
{
f->latchedWriteAddress = f->address;
}
// Latch the data (and update state) on the rising edge of WE
else
{
HandleWriteCycle(f, f->latchedWriteAddress, f->dataInput);
}
}
}
}
else if ((pinIndex = DataPinIndex(f, pin)) >= 0)
{
// Update the current data input state
if (high)
{
f->dataInput |= (1UL << pinIndex);
}
else
{
f->dataInput &= ~(1UL << pinIndex);
}
}
else if ((pinIndex = AddressPinIndex(f, pin)) >= 0)
{
// Update the current address
if (high)
{
f->address |= (1UL << pinIndex);
}
else
{
f->address &= ~(1UL << pinIndex);
}
}
}
static GPIOSimValue ReadGPIOPin(GPIOSimDevice *device, GPIOPin pin)
{
Flash4MBit *f = (Flash4MBit *)device;
// We only read when allowed
if (f->csAsserted && f->oeAsserted)
{
// Figure out if this is a data pin, that's the only pin we ever drive
int8_t dataPinIndex = DataPinIndex(f, pin);
if (dataPinIndex >= 0)
{
uint8_t dataToRead = 0;
if (f->state == FlashSoftwareID)
{
// This is kind of janky, chips might have other internal registers to read. The different chip
// types are differently picky about what bits need to be zeroed for this operation. For the purposes
// of this emulation, just look at the bottom bit to decide manufacturer or device.
if (f->address & 1)
{
dataToRead = DeviceID(f);
}
else
{
dataToRead = ManufacturerID(f);
}
}
// TODO: programming toggle bit state?
else // For example, state is FlashReading, or we're interrupting an unlock sequence
{
// Grab the content from the current address
dataToRead = f->content[f->address];
}
// Drive high or low based on the data we're reading
if (dataToRead & (1 << dataPinIndex))
{
return GPIOSimDrivingHigh;
}
else
{
return GPIOSimDrivingLow;
}
}
}
return GPIOSimNotDriving;
}
static void HandleWriteCycle(Flash4MBit *f, uint32_t address, uint8_t data)
{
uint32_t unlockMask = UnlockAddressMask(f);
switch (f->state)
{
case FlashReadingWriteOrEraseStatus:
// Don't allow any changes to state while a write/erase is active
break;
case FlashReading:
case FlashSoftwareID: // TODO: Does this state even need to exist? Or is the softwareIDActivated bool enough?
default:
if (((address & unlockMask) == (0x55555555UL & unlockMask)) && (data == 0xAA))
{
f->state = FlashUnlocking;
}
else if (data == 0xF0)
{
// Force a reset back to software ID mode
f->softwareIDActivated = false;
f->state = FlashReading;
}
break;
case FlashUnlocking:
if (((address & unlockMask) == (0xAAAAAAAAUL & unlockMask)) && (data == 0x55))
{
f->state = FlashWaitingForCommand;
}
else
{
// Invalid unlock sequence
f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading;
}
break;
case FlashWaitingForCommand:
if ((address & unlockMask) == (0x55555555UL & unlockMask))
{
switch (data)
{
case 0xA0:
f->state = FlashProgrammingByte;
break;
case 0x80:
f->state = FlashEraseUnlocking1;
break;
case 0x90:
f->softwareIDActivated = true;
f->state = FlashSoftwareID;
break;
case 0xF0:
f->softwareIDActivated = false;
f->state = FlashReading;
break;
default:
// Invalid command, go back to the read state we should be in
f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading;
break;
}
}
else
{
// Invalid unlock sequence
f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading;
}
break;
case FlashProgrammingByte:
// Write the data, then begin reading back the status
// Programming is only capable of turning 1 bits into zero bits.
// In other words, programming takes the current byte stored in the flash
// and ANDs it with the value you're trying to program to that byte.
// The resulting value is what gets programmed to the chip.
f->content[address & 0x7FFFF] &= data;
/// TODO: Set up timer for erase?
f->state = FlashReadingWriteOrEraseStatus;
break;
case FlashEraseUnlocking1:
if (((address & unlockMask) == (0x55555555UL & unlockMask)) && (data == 0xAA))
{
f->state = FlashEraseUnlocking2;
}
else
{
f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading;
}
break;
case FlashEraseUnlocking2:
if (((address & unlockMask) == (0xAAAAAAAAUL & unlockMask)) && (data == 0x55))
{
f->state = FlashEraseWaitingForCommand;
}
else
{
// Invalid unlock sequence
f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading;
}
break;
case FlashEraseWaitingForCommand:
if (((address & unlockMask) == (0x55555555UL & unlockMask)) && data == 0x10)
{
// Erase the entire chip
EraseChip(f);
/// TODO: Set up timer for erase?
f->state = FlashReadingWriteOrEraseStatus;
}
else if (data == 0x30)
{
EraseSector(f, address);
/// TODO: Set up timer for erase?
f->state = FlashReadingWriteOrEraseStatus;
}
else
{
// Invalid command/address supplied during erase
f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading;
}
break;
}
}
static void HandleReadCycle(Flash4MBit *f)
{
// We don't care about the address here because most of the output logic is handled in the ReadGPIOPin handler.
// This just makes sure the state machine is up to date after a read cycle.
}
static int8_t DataPinIndex(Flash4MBit *f, GPIOPin pin)
{
for (int i = 0; i < FLASH_4MBIT_DATA_PINS; i++)
{
if (f->dataPins[i].port == pin.port &&
f->dataPins[i].pin == pin.pin)
{
return i;
}
}
return -1;
}
static int8_t AddressPinIndex(Flash4MBit *f, GPIOPin pin)
{
for (int i = 0; i < FLASH_4MBIT_ADDR_PINS; i++)
{
if (f->addressPins[i].port == pin.port &&
f->addressPins[i].pin == pin.pin)
{
return i;
}
}
return -1;
}
static bool IsCSPin(Flash4MBit *f, GPIOPin pin)
{
return f->csPin.port == pin.port && f->csPin.pin == pin.pin;
}
static bool IsOEPin(Flash4MBit *f, GPIOPin pin)
{
return f->oePin.port == pin.port && f->oePin.pin == pin.pin;
}
static bool IsWEPin(Flash4MBit *f, GPIOPin pin)
{
return f->wePin.port == pin.port && f->wePin.pin == pin.pin;
}
static uint32_t UnlockAddressMask(Flash4MBit *f)
{
switch (f->type)
{
case Flash_SST39SF040:
// SST39SF040 only cares about A0 through A14 (low 15 bits)
return 0x7FFFUL;
case Flash_AM29F040B:
// AM329F040B only cares about A0 through A10 (low 11 bits)
return 0x7FFUL;
default:
// Default behavior is we care about all 19 address bits during unlock operations
return 0x7FFFFUL;
}
}
static uint8_t ManufacturerID(Flash4MBit *f)
{
switch (f->type)
{
case Flash_SST39SF040:
return 0xBF;
case Flash_AM29F040B:
return 0x01;
default:
return 0;
}
}
static uint8_t DeviceID(Flash4MBit *f)
{
switch (f->type)
{
case Flash_SST39SF040:
return 0xB7;
case Flash_AM29F040B:
return 0xA4;
default:
return 0;
}
}
static void EraseChip(Flash4MBit *f)
{
memset(f->content, 0xFF, 512*1024);
}
static void EraseSector(Flash4MBit *f, uint32_t address)
{
uint32_t sectorSize;
switch (f->type)
{
case Flash_SST39SF040:
default:
// 4 KB sectors
sectorSize = 4 * 1024;
break;
case Flash_AM29F040B:
// 64 KB sectors
sectorSize = 64 * 1024;
break;
}
// Mask out the low bits of the address so we have the sector start address.
// We're guaranteed that sectorSize is a power of 2, so subtracting 1 will
// give us a mask we can use to turn off address bits to get the start addr.
address &= ~(sectorSize - 1);
// Clear those bytes
memset(f->content + address, 0xFF, sectorSize);
}

79
hal/pc/flash_4mbit.h Normal file
View File

@ -0,0 +1,79 @@
/*
* flash_4mbit.h
*
* Created on: Jul 27, 2021
* Author: Doug
*
* Copyright (C) 2011-2021 Doug Brown
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#ifndef FLASH_4MBIT_H
#define FLASH_4MBIT_H
#include "gpio_sim.h"
#define FLASH_4MBIT_DATA_PINS 8
#define FLASH_4MBIT_ADDR_PINS 19
/// Enum representing the type of 4-megabit flash chip being emulated
typedef enum Flash4MBitDeviceType
{
/// SST/Microchip SST39SF040 4-megabit flash chip
Flash_SST39SF040,
/// AMD/Infineon AM29F040B 4-megabit flash chips
Flash_AM29F040B,
} Flash4MBitDeviceType;
/// Implementation of GPIOSimDevice for a 4-megabit flash chip
typedef struct Flash4MBit
{
/// Base class
GPIOSimDevice base;
/// The type of flash being emulated
Flash4MBitDeviceType type;
/// Current data input state
uint8_t dataInput;
/// Current address
uint32_t address;
/// The write address that has been latched
uint32_t latchedWriteAddress;
/// True if CS is asserted
bool csAsserted;
/// True if OE is asserted
bool oeAsserted;
/// True if WE is asserted
bool weAsserted;
/// True if we've activated software ID mode
bool softwareIDActivated;
/// All of our pins
GPIOPin csPin;
GPIOPin oePin;
GPIOPin wePin;
GPIOPin dataPins[FLASH_4MBIT_DATA_PINS];
GPIOPin addressPins[FLASH_4MBIT_ADDR_PINS];
/// The state -- private so we store as an 8-bit int rather than the enum
uint8_t state;
uint8_t *content;
} Flash4MBit;
void Flash4MBit_Init(Flash4MBit *f, Flash4MBitDeviceType t);
void Flash4MBit_SetControlPins(Flash4MBit *f, GPIOPin cs, GPIOPin oe, GPIOPin we);
void Flash4MBit_SetDataPin(Flash4MBit *f, uint8_t index, GPIOPin pin);
void Flash4MBit_SetAddressPin(Flash4MBit *f, uint8_t index, GPIOPin pin);
#endif // FLASH_4MBIT_H

View File

@ -23,6 +23,12 @@
*/
#include "../gpio.h"
#include "gpio_hw.h"
#include "gpio_sim.h"
static uint32_t directionReg[NUM_SIM_GPIO_PORTS];
static uint32_t pullupReg[NUM_SIM_GPIO_PORTS];
static uint32_t outputReg[NUM_SIM_GPIO_PORTS];
/** Sets the direction of a GPIO pin.
*
@ -31,8 +37,26 @@
*/
void GPIO_SetDirection(GPIOPin pin, bool output)
{
// TODO: Modify direction
(void)pin; (void)output;
if (pin.port < NUM_SIM_GPIO_PORTS)
{
// Figure out if any state is actually changing, and if so, modify it
bool alreadyOutput = directionReg[pin.port] & (1UL << pin.pin);
if (output != alreadyOutput)
{
if (output)
{
directionReg[pin.port] |= (1UL << pin.pin);
// We just became an output, so make sure any sim devices are updated with
// the new driven value
GPIOSim_WritePin(pin, outputReg[pin.port] & (1UL << pin.pin));
}
else
{
directionReg[pin.port] &= ~(1UL << pin.pin);
// We just became an input, there's no state to update really
}
}
}
}
/** Sets whether an input GPIO pin is pulled up
@ -42,8 +66,19 @@ void GPIO_SetDirection(GPIOPin pin, bool output)
*/
void GPIO_SetPullup(GPIOPin pin, bool pullup)
{
// TODO: Modify pullup
(void)pin; (void)pullup;
if (pin.port < NUM_SIM_GPIO_PORTS)
{
// Update the pullup register. No need to update any other state,
// it simply affects the readback simulation
if (pullup)
{
pullupReg[pin.port] |= (1UL << pin.pin);
}
else
{
pullupReg[pin.port] &= ~(1UL << pin.pin);
}
}
}
/** Turns a GPIO pin on (sets it high)
@ -52,8 +87,19 @@ void GPIO_SetPullup(GPIOPin pin, bool pullup)
*/
void GPIO_SetOn(GPIOPin pin)
{
// TODO: Turn on
(void)pin;
if (pin.port < NUM_SIM_GPIO_PORTS)
{
// Determine if we're actually changing the current state, which would
// require us to update simulated devices
bool isOutput = directionReg[pin.port] & (1UL << pin.pin);
bool curOutputValue = outputReg[pin.port] & (1UL << pin.pin);
bool needsNotification = isOutput && !curOutputValue;
outputReg[pin.port] |= (1UL << pin.pin);
if (needsNotification)
{
GPIOSim_WritePin(pin, true);
}
}
}
/** Turns a GPIO pin off (sets it low)
@ -62,8 +108,19 @@ void GPIO_SetOn(GPIOPin pin)
*/
void GPIO_SetOff(GPIOPin pin)
{
// TODO: Turn off
(void)pin;
if (pin.port < NUM_SIM_GPIO_PORTS)
{
// Determine if we're actually changing the current state, which would
// require us to update simulated devices
bool isOutput = directionReg[pin.port] & (1UL << pin.pin);
bool curOutputValue = outputReg[pin.port] & (1UL << pin.pin);
bool needsNotification = isOutput && curOutputValue;
outputReg[pin.port] &= ~(1UL << pin.pin);
if (needsNotification)
{
GPIOSim_WritePin(pin, false);
}
}
}
/** Toggles a GPIO pin
@ -72,8 +129,18 @@ void GPIO_SetOff(GPIOPin pin)
*/
void GPIO_Toggle(GPIOPin pin)
{
// TODO: Toggle
(void)pin;
if (pin.port < NUM_SIM_GPIO_PORTS)
{
// Figure out whether we are turning it on or off, and forward on
if (outputReg[pin.port] & (1UL << pin.pin))
{
GPIO_SetOff(pin);
}
else
{
GPIO_SetOn(pin);
}
}
}
/** Reads the input status of a GPIO pin
@ -83,7 +150,51 @@ void GPIO_Toggle(GPIOPin pin)
*/
bool GPIO_Read(GPIOPin pin)
{
// TODO: Read
(void)pin;
return false;
if (pin.port < NUM_SIM_GPIO_PORTS)
{
// If we are currently configured as an output, just read back the output value.
// We'll pretend that's what our "simulated" hardware does.
if (directionReg[pin.port] & (1UL << pin.pin))
{
return outputReg[pin.port] & (1UL << pin.pin);
}
else
{
// If we're configured as an input, read back the value from any simulators
GPIOSimValue readback = GPIOSim_ReadPin(pin);
switch (readback)
{
case GPIOSimNotDriving:
default:
if (pullupReg[pin.port] & (1UL << pin.pin))
{
// If the pull-up is active and nothing is driving the pin,
// read back as high
return true;
}
else
{
// If the pull-up is not active and nothing is driving the pin,
// it's floating. For the purposes of our simulation, let's return low.
// We could return random values if we wanted...
// TODO: assertion with current GPIO state, stack trace?
return false;
}
case GPIOSimDrivingLow:
return false;
case GPIOSimDrivingHigh:
return true;
case GPIOSimDrivingConflict:
// If it's being driven both high and low, bad things will happen.
// For the purposes of our simulation, read back as high.
// TODO: assertion with current GPIO state, stack trace?
return true;
}
}
}
else
{
// Read values as low if an invalid port is passed in
return false;
}
}

View File

@ -29,13 +29,15 @@
/// Used with the GPIOPin struct.
enum {
/// The PC simulated hardware is laid out in a special way so that pins that
/// belong together are grouped onto virtual "ports". All of the address pins
/// belong together are grouped onto virtual 32-bit "ports". All of the address pins
/// comprise one port, all of the data pins comprise another port, and other
/// miscellaneous pins like LEDs and parallel bus control pins comprise one
/// additional port.
GPIOMISC,
GPIOADDR,
GPIODATA,
/// We also have a define to know how many actual ports we have
NUM_SIM_GPIO_PORTS
};
#endif /* HAL_PC_GPIO_HW_H_ */

100
hal/pc/gpio_sim.c Normal file
View File

@ -0,0 +1,100 @@
/*
* gpio_sim.c
*
* Created on: Jul 31, 2021
* Author: Doug
*
* Copyright (C) 2011-2021 Doug Brown
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "gpio_sim.h"
#include <stdlib.h>
static GPIOSimDevice *gpioSims = NULL;
void GPIOSim_AddDevice(GPIOSimDevice *device)
{
// We keep a super simple linked list, ordering doesn't matter.
// For simplicity, prepend this device to the linked list.
device->next = gpioSims;
gpioSims = device;
}
void GPIOSim_WritePin(GPIOPin pin, bool high)
{
// Give each simulated device a chance to handle this event
GPIOSimDevice *device = gpioSims;
while (device)
{
if (device->functions && device->functions->drivePin)
{
device->functions->drivePin(device, pin, high);
}
device = device->next;
}
}
GPIOSimValue GPIOSim_ReadPin(GPIOPin pin)
{
GPIOSimValue retval = GPIOSimNotDriving;
// Give each simulated device a chance to handle this read
GPIOSimDevice *device = gpioSims;
while (device)
{
if (device->functions && device->functions->readPin)
{
GPIOSimValue val = device->functions->readPin(device, pin);
switch (val)
{
case GPIOSimNotDriving:
default:
// Do nothing if we're not driving this pin
break;
case GPIOSimDrivingLow:
// If we haven't found anything driving it yet, we can be the driver
if (retval == GPIOSimNotDriving)
{
retval = GPIOSimDrivingLow;
}
// If we already found something driving it high, mark the error condition
else if (retval == GPIOSimDrivingHigh)
{
retval = GPIOSimDrivingConflict;
}
break;
case GPIOSimDrivingHigh:
// If we haven't found anything driving it yet, we can be the driver
if (retval == GPIOSimNotDriving)
{
retval = GPIOSimDrivingHigh;
}
// If we already found something driving it low, mark the error condition
else if (retval == GPIOSimDrivingLow)
{
retval = GPIOSimDrivingConflict;
}
break;
}
}
device = device->next;
}
// All done, return retval
return retval;
}

62
hal/pc/gpio_sim.h Normal file
View File

@ -0,0 +1,62 @@
/*
* gpio_sim.h
*
* Created on: Jul 31, 2021
* Author: Doug
*
* Copyright (C) 2011-2021 Doug Brown
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#ifndef GPIO_SIM_H
#define GPIO_SIM_H
#include "../gpio.h"
/// Enum describing the various ways a simulated GPIO device can be driving a GPIO pin
typedef enum GPIOSimValue
{
/// The simulated GPIO is not driving this pin
GPIOSimNotDriving,
/// The simulated GPIO is driven low
GPIOSimDrivingLow,
/// The simulated GPIO is driven high
GPIOSimDrivingHigh,
/// It's being driven both high and low, uh oh.
/// Actual devices don't return this value, but it's used as a return value for GPIOSim_ReadPin
GPIOSimDrivingConflict
} GPIOSimValue;
typedef struct GPIOSimDevice GPIOSimDevice;
typedef struct GPIOSimDeviceFunctions
{
void (*drivePin)(GPIOSimDevice *device, GPIOPin pin, bool high);
GPIOSimValue (*readPin)(GPIOSimDevice *device, GPIOPin pin);
} GPIOSimDeviceFunctions;
struct GPIOSimDevice
{
GPIOSimDeviceFunctions const *functions;
GPIOSimDevice *next;
};
void GPIOSim_AddDevice(GPIOSimDevice *device);
void GPIOSim_WritePin(GPIOPin pin, bool high);
GPIOSimValue GPIOSim_ReadPin(GPIOPin pin);
#endif // GPIO_SIM_H

View File

@ -76,8 +76,11 @@ void ParallelBus_Init(void)
*/
void ParallelBus_SetAddress(uint32_t address)
{
// TODO: Fill out entire address in GPIOADDR
(void)address;
for (int i = 0; i <= PARALLEL_BUS_HIGHEST_ADDRESS_LINE; i++)
{
GPIOPin addrPin = {GPIOADDR, i};
GPIO_Set(addrPin, address & (1UL << i));
}
}
/** Sets the output data on the 32-bit data bus
@ -86,8 +89,11 @@ void ParallelBus_SetAddress(uint32_t address)
*/
void ParallelBus_SetData(uint32_t data)
{
// TODO: Fill out entire data in GPIODATA
(void)data;
for (int i = 0; i < 32; i++)
{
GPIOPin dataPin = {GPIODATA, i};
GPIO_Set(dataPin, data & (1UL << i));
}
}
/** Sets the output value of the CS pin
@ -126,8 +132,11 @@ void ParallelBus_SetWE(bool high)
*/
void ParallelBus_SetAddressDir(uint32_t outputs)
{
// TODO: Set direction of GPIOADDR
(void)outputs;
for (int i = 0; i <= PARALLEL_BUS_HIGHEST_ADDRESS_LINE; i++)
{
GPIOPin addrPin = {GPIOADDR, i};
GPIO_SetDirection(addrPin, outputs & (1UL << i));
}
}
/** Sets which pins on the 32-bit data bus should be outputs
@ -140,8 +149,11 @@ void ParallelBus_SetAddressDir(uint32_t outputs)
*/
void ParallelBus_SetDataDir(uint32_t outputs)
{
// TODO: Set direction of GPIODATA
(void)outputs;
for (int i = 0; i < 32; i++)
{
GPIOPin dataPin = {GPIODATA, i};
GPIO_SetDirection(dataPin, outputs & (1UL << i));
}
}
/** Sets the direction of the CS pin
@ -189,8 +201,11 @@ void ParallelBus_SetWEDir(bool output)
*/
void ParallelBus_SetAddressPullups(uint32_t pullups)
{
// TODO: Set pullups on GPIOADDR
(void)pullups;
for (int i = 0; i <= PARALLEL_BUS_HIGHEST_ADDRESS_LINE; i++)
{
GPIOPin addrPin = {GPIOADDR, i};
GPIO_SetPullup(addrPin, pullups & (1UL << i));
}
}
/** Sets which pins on the 32-bit data bus should be pulled up (if inputs)
@ -203,8 +218,11 @@ void ParallelBus_SetAddressPullups(uint32_t pullups)
*/
void ParallelBus_SetDataPullups(uint32_t pullups)
{
// TODO: Set pullups on GPIODATA
(void)pullups;
for (int i = 0; i < 32; i++)
{
GPIOPin dataPin = {GPIODATA, i};
GPIO_SetPullup(dataPin, pullups & (1UL << i));
}
}
/** Sets whether the CS pin is pulled up, if it's an input.
@ -252,8 +270,16 @@ void ParallelBus_SetWEPullup(bool pullup)
*/
uint32_t ParallelBus_ReadAddress(void)
{
// TODO: Read GPIOADDR
return 0;
uint32_t readback = 0;
for (int i = 0; i <= PARALLEL_BUS_HIGHEST_ADDRESS_LINE; i++)
{
GPIOPin addrPin = {GPIOADDR, i};
if (GPIO_Read(addrPin))
{
readback |= (1UL << i);
}
}
return readback;
}
/** Reads the current data on the 32-bit data bus.
@ -262,8 +288,16 @@ uint32_t ParallelBus_ReadAddress(void)
*/
uint32_t ParallelBus_ReadData(void)
{
// TODO: Read GPIODATA
return 0;
uint32_t readback = 0;
for (int i = 0; i < 32; i++)
{
GPIOPin dataPin = {GPIODATA, i};
if (GPIO_Read(dataPin))
{
readback |= (1UL << i);
}
}
return readback;
}
/** Reads the status of the CS pin, if it's set as an input.

View File

@ -1,7 +1,9 @@
# Create a list of all source files specific to the PC
set(HWSOURCES
hal/pc/board.c
hal/pc/flash_4mbit.c
hal/pc/gpio.c
hal/pc/gpio_sim.c
hal/pc/main.cpp
hal/pc/mainwindow.cpp
hal/pc/mainwindow.ui