Skip to content

Commit

Permalink
Build out the editor toolbars
Browse files Browse the repository at this point in the history
  • Loading branch information
bbycroft committed Oct 22, 2023
1 parent d0f50d5 commit 95ace44
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 52 deletions.
22 changes: 11 additions & 11 deletions src/app/cpu/guide/01-riscv-basic/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export default function Page() {
This is stored in a <em>register</em> as a 32 bit number.
</Para>
<Para>
Because the PC is an address, it points to a byte in memory. And then since each instruction
Because the PC is an address, it points to a byte (8 bits) in memory. And then since each instruction
is 4 bytes long, we'll need to increment the PC by 4 each time we execute an instruction. Let's see this in action:
</Para>

Expand Down Expand Up @@ -187,7 +187,7 @@ export default function Page() {
</InstructionTable>

<Para>
These 10 instructions can be defined with 4 bits, so that's what is used in the instruction decoder. We also
These 10 instructions can be defined with 4 bits (2^3 = 8: too small; 2^4 = 16: sufficient), so that's what is used in the instruction decoder. We also
pass a few extra bits to the ALU, which tell it whether to do anything at all, as well as whether
it should produce a <em>branch</em> bit. Let's hook up the ALU, and see it in action:
</Para>
Expand Down Expand Up @@ -269,7 +269,7 @@ export default function Page() {
<GuideSection title={"B-type (branching) instructions"}>

<Para>
The next instruction type we'll add is the B-type (branching) instruction. This is the first time we'll be making
The next instruction type we'll support is the B-type (branching) instruction. This is the first time we'll be making
the PC register jump to something other than PC + 4. This instruction forms the basis of things like if/else statements
and for-loops. The instruction is a bit like the I-type instruction: it contains two register values and an immediate.
However, the 2 registers represent two values to compare (i.e. reads), and the immediate is used as an address offset.
Expand All @@ -287,14 +287,14 @@ export default function Page() {

<Para>
We pass the two register values to the ALU, and perform a comparison on them. There are a few different comparison
types, and each of them produces a 1-bit value: is the condition met or not? We then use this 1-bit value to choose
types, and each of them produces a 1-bit value: "Is the condition met?". We then use this 1-bit value to choose
whether we want to jump or not.
</Para>

<Para>
The instruction decoder is already sending the ALU a <em>branch</em> bit, which tells it to produce a 1-bit value, and
now we need to wire that value to a mux. That mux can then select between the PC + 4 value, and the PC + 4 + offset value.
Since they're both offsets, we can select between '4' & 'offset', where the offset comes from the instruction decoder.
The instruction decoder is already sending the ALU a <em>branch</em> bit, which tells it to output a 1-bit value, and
now we need to wire that output value to a mux. That mux can then select between the <code>PC + 4</code> value, and the <code>PC + offset</code> value.
Since they're both offsets, we can select between <code>4</code> & <code>offset</code>, where the offset comes from the instruction decoder.
</Para>

<SchematicView schematicId={"b-type"} caption={"Ins-decode with branching"} />
Expand All @@ -320,8 +320,8 @@ export default function Page() {

<Para>
These two, <Ins>jal</Ins> (jump & link) and <Ins>jalr</Ins> (jump & link register), are fairly similar. They both jump to
a new address (the jump part), and they both store the value (PC + 4) in a register (the link part). The difference is that
<Ins>jal</Ins> calculates the new address as (PC + 20-bit-imm), and <Ins>jalr</Ins> calculates it as (reg[rs1] + 12-bit-imm).
a new address (the jump part), and they both store the value <code>PC + 4</code> in a register (the link part). The difference is that
<Ins>jal</Ins> calculates the new address as <code>PC + 20-bit-imm</code>, and <Ins>jalr</Ins> calculates it as <code>reg[rs1] + 12-bit-imm</code>.
</Para>

<Para>
Expand All @@ -333,8 +333,8 @@ export default function Page() {
<SchematicView schematicId={"b-type"} caption={"Add PC mux wires"} />

<Para>
To handle the ALU results, we need to add a couple more mux's. Normally, PC + [offset] goes back to PC, and ALU output
goes to the register file. But for our jump instructions, we do the opposite: PC + [offset] goes to the register file, and
To handle the ALU results, we need to add a couple more mux's. Normally, <code>PC + [offset]</code> goes back to PC, and ALU output
goes to the register file. But for our jump instructions, we do the opposite: <code>PC + [offset]</code> goes to the register file, and
ALU output goes to PC. To do this, we can use a couple of mux's to select the correct source. This can be controlled
by a single signal from the instruction decoder.
</Para>
Expand Down
15 changes: 15 additions & 0 deletions src/cpu/ComponentAdder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from "react";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ToolbarButton } from "./toolbars/ToolbarBasics";

export const ComponentAdder: React.FC<{
}> = () => {

return <>
<ToolbarButton className="px-4">
<FontAwesomeIcon icon={faPlus} className="mr-2" />
Add Component
</ToolbarButton>
</>;
};
3 changes: 2 additions & 1 deletion src/cpu/CpuCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { palette } from "./palette";
import { drawGrid, makeCanvasFont } from "./CanvasRenderHelpers";
import { computeSubLayoutMatrix } from "./SubSchematics";
import { computeModelBoundingBox, computeZoomExtentMatrix, createCpuEditorState } from "./ModelHelpers";
import { MainToolbar } from "./CpuToolbars";
import { MainToolbar } from "./toolbars/CpuToolbars";

interface ICanvasDragState {
mtx: AffineMat2d;
Expand Down Expand Up @@ -271,6 +271,7 @@ export const CpuCanvas: React.FC<{
</CanvasEventHandler>}
<div className={s.toolsLeftTop}>
{!readonly && <>
<CompLibraryView />
<CompExampleView />
<SchematicLibraryView />
</>}
Expand Down
1 change: 1 addition & 0 deletions src/cpu/CpuModel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ export interface ISchematic {
}

export interface IEditSnapshot {
name: string;
selected: IElRef[];

// schematic
Expand Down
1 change: 1 addition & 0 deletions src/cpu/ModelHelpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export function createCpuEditorState(): IEditorState {
export function constructEditSnapshot(): IEditSnapshot {
return {
selected: [],
name: "",

nextWireId: 0,
nextCompId: 0,
Expand Down
3 changes: 2 additions & 1 deletion src/cpu/guide/CpuEnabledGuide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CPUDirectory, guideEntries } from './GuideIndex';
import { Header } from '@/src/homepage/Header';
import { NavSidebar } from './NavSidebar';
import { useCreateGlobalKeyboardDocumentListener } from '@/src/utils/keyboard';
import './guideStyle.css';

export const CpuEnabledGuide: React.FC<{
dir: CPUDirectory;
Expand All @@ -17,7 +18,7 @@ export const CpuEnabledGuide: React.FC<{
<Header title={entry.name} />
<div className='flex flex-grow items-start'>
<NavSidebar className='w-3/12 bg-slate-100 min-h-full' activeEntry={dir} />
<div className='w-9/12 flex flex-col py-2 mb-[10rem]'>
<div className='guide-style w-9/12 flex flex-col py-2 mb-[10rem]'>
{children}
</div>
<div className='w-3/12 bg-slate-100 min-h-full'>
Expand Down
5 changes: 3 additions & 2 deletions src/cpu/guide/InstructionDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ export const InstructionDetail: React.FC<{
{bitNodes.map((node, i) => {
let type = node.section?.type ?? BitRangeType.Code;
let valStr = bitRangeToChar(type).repeat(node.nBits);
if (isNotNil(node.value)) {
let explicitValue = isNotNil(node.value);
if (explicitValue && isNotNil(node.value)) {
valStr = node.value.toString(2).padStart(node.nBits, '0');
}
return <span key={i} className={clsx(bitRangeTypeColor(type))}>
return <span key={i} className={clsx(bitRangeTypeColor(type), explicitValue ? 'font-bold' : '')}>
{valStr}
</span>;
})}
Expand Down
7 changes: 7 additions & 0 deletions src/cpu/guide/guideStyle.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

.guide-style code {
padding: 2px 4px;
margin: 0 1px;
background-color: #f0f0f0;
border-radius: 4px;
}
1 change: 1 addition & 0 deletions src/cpu/schematics/SchematicLibrary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export class SchematicLibrary {
let snapshot = createInitialEditSnapshot();
snapshot = wiresFromLsState(snapshot, lsSchematic.model, compLibrary);
snapshot = addCompArgsToSnapshot(snapshot, compArgs);
snapshot.name = lsSchematic.name;

customSchematics.set(lsSchematic.id, {
id: lsSchematic.id,
Expand Down
62 changes: 26 additions & 36 deletions src/cpu/CpuToolbars.tsx → src/cpu/toolbars/CpuToolbars.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import React, { useEffect, useState } from 'react';
import { faBook, faCheck, faChevronRight, faClockRotateLeft, faCodeFork, faExpand, faFloppyDisk, faForward, faForwardFast, faForwardStep, faPlay, faPowerOff, faRedo, faRotateLeft, faTimes, faUndo } from '@fortawesome/free-solid-svg-icons';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { assignImm, isNotNil } from '../utils/data';
import { useGlobalKeyboard, KeyboardOrder, isKeyWithModifiers, Modifiers } from '../utils/keyboard';
import { IBaseEvent } from '../utils/pointer';
import { redoAction, undoAction, useEditorContext } from './Editor';
import { assignImm, isNotNil } from '../../utils/data';
import { useGlobalKeyboard, KeyboardOrder, isKeyWithModifiers, Modifiers } from '../../utils/keyboard';
import { IBaseEvent } from '../../utils/pointer';
import { editLayout, redoAction, undoAction, useEditorContext } from '../Editor';
import clsx from 'clsx';
import { Tooltip } from '../utils/Tooltip';
import { resetExeModel, stepExecutionCombinatorial, stepExecutionLatch } from './CpuExecution';
import { modifiersToString } from './Keymap';
import { Tooltip } from '../../utils/Tooltip';
import { resetExeModel, stepExecutionCombinatorial, stepExecutionLatch } from '../CpuExecution';
import { modifiersToString } from '../Keymap';
import { ComponentAdder } from '../ComponentAdder';
import { ToolbarButton, ToolbarDivider } from './ToolbarBasics';

export const MainToolbar: React.FC<{

Expand Down Expand Up @@ -47,6 +49,10 @@ export const MainToolbar: React.FC<{
setEditorState(a => assignImm(a, { compLibraryVisible: !a.compLibraryVisible }));
}

function handleNameChange(ev: IBaseEvent, value: string, end: boolean) {
setEditorState(editLayout(end, a => assignImm(a, { name: value })));
}

let undoAvailable = editorState.undoStack.length > 0;
let redoAvailable = editorState.redoStack.length > 0;

Expand All @@ -70,38 +76,22 @@ export const MainToolbar: React.FC<{
<ToolbarDivider />

<ViewportControls />
</div>;
};

const ToolbarButton: React.FC<{
className?: string,
icon?: IconProp,
text?: string,
disabled?: boolean;
notImpl?: boolean;
tip?: React.ReactNode,
children?: React.ReactNode,
onClick?: (ev: IBaseEvent) => void,
}> = ({ className, icon, text, disabled, notImpl, tip, children, onClick }) => {

let btn = <button
className={clsx(className, 'group self-stretch min-w-[3rem] flex items-center justify-center disabled:opacity-40 rounded-md my-1', !disabled && "hover:bg-blue-300 active:bg-blue-400", notImpl && "bg-red-100")}
disabled={disabled}
onClick={onClick}
>
{text}
{icon && <FontAwesomeIcon icon={icon} className={clsx('text-gray-600 disabled:text-gray-300', text && 'ml-3')} />}
{children}
</button>;

return tip ? <Tooltip tip={tip}>{btn}</Tooltip> : btn;
};
<ToolbarDivider />

<ComponentAdder />

const ToolbarDivider: React.FC<{ className?: string }> = ({ className }) => {
return <div className={clsx(className, 'w-[1px] bg-slate-300 my-1 mx-2')} />;
<ToolbarDivider />

<div className='min-w-[200px] h-full flex items-center'>
<div className='mr-2'>Name:</div>
<ToolbarNameEditor value={editorState.snapshot.name} setValue={handleNameChange} />
</div>
</div>;
};



const ToolbarNameEditor: React.FC<{
value: string,
setValue: (ev: IBaseEvent, value: string, end: boolean) => void,
Expand Down Expand Up @@ -143,9 +133,9 @@ const ToolbarNameEditor: React.FC<{
});

return <>
{!isEditingName && <div className='hover:bg-slate-600 px-1 rounded' onClick={() => setEditingName(value)}>{value}</div>}
{!isEditingName && <div className='hover:bg-slate-300 bg-slate-100 my-1 px-2 py-1 rounded flex-1' onClick={() => setEditingName(value)}>{value}</div>}
{isEditingName && <>
<input ref={setInputEl} type='text' className='bg-slate-600 px-1 mr-1 rounded focus:outline-none focus:border-slate-500 w-[16rem] max-w-[20rem] flex-shrink' value={editingName || ''} onChange={ev => setEditingName(ev.target.value)} />
<input ref={setInputEl} type='text' className='bg-slate-300 px-2 py-1 mr-1 my-1 rounded focus:outline-none focus:border-slate-500 w-[16rem] max-w-[20rem] flex-shrink' value={editingName || ''} onChange={ev => setEditingName(ev.target.value)} />
<button className={"px-1 mx-1 hover:text-slate-200"} onClick={applyEditName}>
<FontAwesomeIcon icon={faCheck} />
</button>
Expand Down
35 changes: 35 additions & 0 deletions src/cpu/toolbars/ToolbarBasics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Tooltip } from '@/src/utils/Tooltip';
import { IBaseEvent } from '@/src/utils/pointer';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import clsx from 'clsx';
import React from 'react';

export const ToolbarDivider: React.FC<{ className?: string }> = ({ className }) => {
return <div className={clsx(className, 'w-[1px] bg-slate-300 my-1 mx-2')} />;
};


export const ToolbarButton: React.FC<{
className?: string,
icon?: IconProp,
text?: string,
disabled?: boolean;
notImpl?: boolean;
tip?: React.ReactNode,
children?: React.ReactNode,
onClick?: (ev: IBaseEvent) => void,
}> = ({ className, icon, text, disabled, notImpl, tip, children, onClick }) => {

let btn = <button
className={clsx(className, 'group self-stretch min-w-[3rem] flex items-center justify-center disabled:opacity-40 rounded-md my-1', !disabled && "hover:bg-blue-300 active:bg-blue-400", notImpl && "bg-red-100")}
disabled={disabled}
onClick={onClick}
>
{text}
{icon && <FontAwesomeIcon icon={icon} className={clsx('text-gray-600 disabled:text-gray-300', text && 'ml-3')} />}
{children}
</button>;

return tip ? <Tooltip tip={tip}>{btn}</Tooltip> : btn;
};
2 changes: 1 addition & 1 deletion src/utils/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { CSSProperties, useEffect, useLayoutEffect, useMemo, useState } from "react";
import React, { CSSProperties, useMemo, useState } from "react";
import { Portal } from "./Portal";
import clsx from "clsx";
import { useResizeChangeHandler } from "./layout";
Expand Down

0 comments on commit 95ace44

Please sign in to comment.