Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 81 additions & 37 deletions app/src/app/api/llm/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import { LLMFactory, ProviderType } from '@/lib/llm/factory';
import { constructPrompt } from '@/lib/llm/context';
import { checkRateLimit, rateLimitHeaders, getClientId, LLM_RATE_LIMIT } from '@/lib/rateLimit';

// Helper to extract code block
function extractCodeBlock(text: string): string {
const match = text.match(/```(?:verilog|systemverilog)?\n([\s\S]*?)\n```/);
return match ? match[1] : text;
}

export async function POST(req: NextRequest) {
try {
// Rate limiting check
Expand All @@ -27,51 +33,25 @@ export async function POST(req: NextRequest) {
userQuery,
projectFiles = [],
systemOptions = {},
stream = false
stream = false,
mode = 'standard' // 'standard' | 'iterative'
} = body;

const llmProvider = LLMFactory.createProvider(provider as ProviderType, { defaultModel: model });

const fullPrompt = constructPrompt(userQuery, projectFiles, { systemOptions });

const requestPayload = {
systemPrompt: fullPrompt.split('USER REQUEST:')[0], // Split strictly for API that support 'system' role
userPrompt: `USER REQUEST:\n${userQuery}`, // Or keep full prompt if needed, but providers split system/user
// Actually, constructPrompt combines them. Providers expect separate system/user often.
// Let's adjust usage:
// constructPrompt returns a big string.
// But LLMRequest interface has systemPrompt and userPrompt.
// We should probably rely on constructPrompt for the "System" part primarily,
// but generic providers might work better if we separate them.
//
// Valid Strategy:
// 1. extract system part from constructPrompt logic (or call getSystemPrompt directly)
// 2. build user part with context and query
};

// Refined prompt handling
// Base Prompt Construction
const systemPromptText = constructPrompt('', [], { systemOptions }).split('USER REQUEST:')[0].trim();
// Re-construct the user logic part (Context + Examples + Query)
// We can cheat: constructPrompt(userQuery, projectFiles) contains everything.
// If we pass everything as "User Prompt" it works for most models, but setting "System Prompt" is better.

// Let's do:
// System = getSystemPrompt()
// User = Context + Examples + Query

// We need to import helper functions again properly or just use constructPrompt's logic.
// For now, let's use the constructPrompt result as the USER prompt (and empty system default? no, system is strong).
// Better: let's update constructPrompt in future to support separation.
// Current workaround:
let currentPrompt = constructPrompt(userQuery, projectFiles, { systemOptions }).replace(systemPromptText, '').trim();

// Initial Generation
const llmRequest = {
systemPrompt: systemPromptText, // The Persona + Rues
userPrompt: fullPrompt.replace(systemPromptText, '').trim(), // The Context + Examples + Query
systemPrompt: systemPromptText,
userPrompt: currentPrompt,
maxTokens: 4096
};

// If streaming, we just return the stream (Iterative not supported in streaming yet)
if (stream) {
// Simple streaming response setup
const encoder = new TextEncoder();
const customStream = new ReadableStream({
async start(controller) {
Expand All @@ -85,11 +65,75 @@ export async function POST(req: NextRequest) {
return new NextResponse(customStream, {
headers: { 'Content-Type': 'text/plain; charset=utf-8' }
});
} else {
const response = await llmProvider.generate(llmRequest);
return NextResponse.json(response);
}

// Standard Generation
let response = await llmProvider.generate(llmRequest);

// Iterative Refinement Loop
if (mode === 'iterative') {
const { parse } = await import('@/lib/verilog/parser');
const { cstToAst } = await import('@/lib/verilog/visitor');
const { validateModule } = await import('@/lib/verilog/validator');

let iterations = 0;
const maxIterations = 3;
let isValid = false;

while (iterations < maxIterations && !isValid) {
try {
const code = extractCodeBlock(response.content);
const parseResult = parse(code);

if (parseResult.errors.length > 0) {
// Syntax errors
const errorMsg = parseResult.errors.map(e => `Line ${e.line}: ${e.message}`).join('\n');
const refinementPrompt = `
The generated code had syntax errors:
${errorMsg}

Please fix the syntax and regenerate the code.
`;
llmRequest.userPrompt += `\n\nAssistant: ${response.content}\n\nUser: ${refinementPrompt}`;
response = await llmProvider.generate(llmRequest);
iterations++;
continue;
}

// CST -> AST -> Validate
const ast = cstToAst(parseResult.cst);
const validation = validateModule(ast);

if (validation.isValid) {
isValid = true;
break;
}

// Logic errors
const errors = validation.errors.map(e => `- Line ${e.line}: ${e.message}`).join('\n');
const refinementPrompt = `
The previous generation had the following lint errors:
${errors}

Please fix these errors and regenerate the full module code.
`;
llmRequest.userPrompt += `\n\nAssistant: ${response.content}\n\nUser: ${refinementPrompt}`;

console.log(`[Iterative] Retrying... (${iterations + 1}) Errors: ${validation.errors.length}`);

response = await llmProvider.generate(llmRequest);
iterations++;

} catch (err: any) {
console.error('Refinement loop error:', err);
break; // Exit loop on unexpected error to return what we have
}
}
}


return NextResponse.json(response);

} catch (error: any) {
console.error('LLM API Error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
Expand Down
69 changes: 69 additions & 0 deletions app/src/app/api/llm/testbench/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@

import { NextRequest, NextResponse } from 'next/server';
import { LLMFactory } from '@/lib/llm/factory';
import { constructPrompt } from '@/lib/llm/context';
import { checkRateLimit, rateLimitHeaders, getClientId, LLM_RATE_LIMIT } from '@/lib/rateLimit';

export async function POST(req: NextRequest) {
try {
// Rate limiting check
const clientId = getClientId(req);
const rateLimitResult = checkRateLimit(`llm:generate:${clientId}`, LLM_RATE_LIMIT);

if (!rateLimitResult.success) {
return NextResponse.json(
{ error: 'Rate limit exceeded. Please try again later.' },
{
status: 429,
headers: rateLimitHeaders(rateLimitResult, LLM_RATE_LIMIT)
}
);
}

const body = await req.json();
const { code, moduleName, provider = 'anthropic', model } = body;

const llmProvider = LLMFactory.createProvider(provider, { defaultModel: model });

const prompt = `
You are an expert Verilog Verification Engineer.
Your task is to write a comprehensive, self-checking testbench for the following Verilog module.

MODULE CODE:
\`\`\`verilog
${code}
\`\`\`

REQUIREMENTS:
1. The testbench module name should be \`${moduleName}_tb\`.
2. Instantiate the DUT (Device Under Test).
3. Generate a clock (if the module has one).
4. Apply a proper reset sequence.
5. Provide test vectors to cover major functionality.
6. Use \`$display\` to log results and \`$finish\` to end simulation.
7. Check outputs using \`if (out !== expected) $error(...)\`.
8. Include \`initial begin $dumpfile("waveform.vcd"); $dumpvars(0, ${moduleName}_tb); end\` for VCD generation.
9. Output ONLY the Verilog code for the testbench, wrapped in a code block.

GENERATE TESTBENCH:
`;

const llmRequest = {
systemPrompt: 'You are an expert Verilog verification engineer.',
userPrompt: prompt,
maxTokens: 4096
};

const response = await llmProvider.generate(llmRequest);

// Extract code block
const match = response.content.match(/```(?:verilog|systemverilog)?\n([\s\S]*?)\n```/);
const tbCode = match ? match[1] : response.content;

return NextResponse.json({ code: tbCode });

} catch (error: any) {
console.error('Testbench Gen Error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
26 changes: 26 additions & 0 deletions app/src/app/api/verification/prove/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

import { NextRequest, NextResponse } from 'next/server';
import { runProof } from '@/lib/simulation/runner';
import { z } from 'zod';

const schema = z.object({
code: z.string(),
});

export async function POST(req: NextRequest) {
try {
const body = await req.json();
const { code } = schema.parse(body);

// Rate limiting would go here

const result = await runProof(code);

return NextResponse.json(result);
} catch (error: any) {
return NextResponse.json(
{ error: error.message || 'Verification failed' },
{ status: 500 }
);
}
}
46 changes: 45 additions & 1 deletion app/src/app/editor/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import FileTree from '@/components/layout/FileTree';
import EditorTabs from '@/components/layout/EditorTabs';
import Toolbar from '@/components/layout/Toolbar';
import { useEditorStore } from '@/lib/store/editor';
import { useFSMStore } from '@/lib/fsm/store';
import { VCDData } from '@/lib/waveform/vcdParser';

// Dynamic imports for heavy components (code splitting)
Expand Down Expand Up @@ -40,6 +41,7 @@ function EditorLoading({ label }: { label: string }) {

export default function IDEPage() {
const { files, activeFileId } = useEditorStore();
const { fsm, setActiveState } = useFSMStore();
const activeFile = files.find(f => f.id === activeFileId);

const [vcdData, setVcdData] = useState<VCDData | null>(null);
Expand All @@ -50,6 +52,43 @@ export default function IDEPage() {
setShowWaveform(true);
};

const handleCursorChange = (time: number | null) => {
if (time === null || !vcdData) {
setActiveState(null);
return;
}

// Find state signal (heuristic: contains 'state' but not 'next')
const stateSignalName = Array.from(vcdData.signals.keys()).find(k =>
k.toLowerCase().includes('state') && !k.toLowerCase().includes('next')
);

if (stateSignalName) {
const changes = vcdData.changes.get(stateSignalName);
if (changes) {
let value = '';
for (const change of changes) {
if (change.time > time) break;
value = change.value;
}

if (value) {
// Logic: Map VCD value to FSM state
// value is e.g. "10" (binary)
const normalizedSignalVal = parseInt(value, 2);

// Simple heuristic: Assume binary encoding corresponds to states array index
if (!isNaN(normalizedSignalVal) && normalizedSignalVal < fsm.states.length) {
const activeState = fsm.states[normalizedSignalVal];
if (activeState) {
setActiveState(activeState.id, time);
}
}
}
}
}
};

return (
<div className="flex h-screen w-screen overflow-hidden bg-[var(--background)] gradient-mesh">
{/* File Tree Sidebar */}
Expand Down Expand Up @@ -132,7 +171,12 @@ export default function IDEPage() {
</svg>
</button>
<Suspense fallback={<EditorLoading label="Loading..." />}>
<WaveformViewer data={vcdData} width={1000} height={300} />
<WaveformViewer
data={vcdData}
width={1000}
height={300}
onCursorChange={handleCursorChange}
/>
</Suspense>
</div>
</Panel>
Expand Down
22 changes: 17 additions & 5 deletions app/src/components/fsm/StateNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,37 @@ import { Play } from 'lucide-react';
interface StateNodeData {
label: string;
isInitial?: boolean;
isActive?: boolean; // For simulation highlighting
onLabelChange?: (id: string, newLabel: string) => void;
outputs?: Array<{ signal: string; value: string }>;
}

export const StateNode = memo(({ data, id, selected }: NodeProps<StateNodeData>) => {
const isActive = data.isActive;

return (
<div
className={`
relative min-w-[130px] rounded-xl border-2 bg-white px-4 py-3
shadow-md transition-all duration-200 group
${selected
? 'border-indigo-500 shadow-lg shadow-indigo-500/20 ring-4 ring-indigo-100'
: 'border-slate-200 hover:border-slate-300 hover:shadow-lg'
${isActive
? 'border-emerald-500 shadow-lg shadow-emerald-500/40 ring-4 ring-emerald-100 animate-pulse'
: selected
? 'border-indigo-500 shadow-lg shadow-indigo-500/20 ring-4 ring-indigo-100'
: 'border-slate-200 hover:border-slate-300 hover:shadow-lg'
}
${data.isInitial ? 'border-l-4 border-l-indigo-600' : ''}
${data.isInitial && !isActive ? 'border-l-4 border-l-indigo-600' : ''}
`}
>
{/* Active state indicator (simulation) */}
{isActive && (
<div className="absolute -top-2 -right-2 w-4 h-4 rounded-full bg-emerald-500 flex items-center justify-center">
<div className="w-2 h-2 rounded-full bg-white animate-ping" />
</div>
)}

{/* Initial state animated indicator */}
{data.isInitial && (
{data.isInitial && !isActive && (
<div className="absolute -left-1 top-1/2 -translate-y-1/2 w-2 h-2 rounded-full bg-indigo-600 animate-pulse" />
)}

Expand Down
Loading
Loading